first commit
This commit is contained in:
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitf3438a4bfc092aad40a104edf0a3eb02::getLoader();
|
||||
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2013, Ben 'DASPRiD' Scholzen
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
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.
|
||||
|
||||
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 OWNER 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.
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode;
|
||||
|
||||
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
|
||||
|
||||
/**
|
||||
* Module for generating QR codes.
|
||||
*/
|
||||
class Module implements AutoloaderProviderInterface
|
||||
{
|
||||
/**
|
||||
* Get autoloader config.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAutoloaderConfig()
|
||||
{
|
||||
return array(
|
||||
'Zend\Loader\ClassMapAutoloader' => array(
|
||||
__DIR__ . '/autoload_classmap.php',
|
||||
),
|
||||
'Zend\Loader\StandardAutoloader' => array(
|
||||
'namespaces' => array(
|
||||
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
QR Code generator
|
||||
=================
|
||||
|
||||
Master: [](http://travis-ci.org/Bacon/BaconQrCode)
|
||||
|
||||
Introduction
|
||||
------------
|
||||
BaconQrCode is a port of QR code portion of the ZXing library. It currently
|
||||
only features the encoder part, but could later receive the decoder part as
|
||||
well.
|
||||
|
||||
As the Reed Solomon codec implementation of the ZXing library performs quite
|
||||
slow in PHP, it was exchanged with the implementation by Phil Karn.
|
||||
|
||||
|
||||
Example usage
|
||||
-------------
|
||||
```php
|
||||
$renderer = new \BaconQrCode\Renderer\Image\Png();
|
||||
$renderer->setHeight(256);
|
||||
$renderer->setWidth(256);
|
||||
$writer = new \BaconQrCode\Writer($renderer);
|
||||
$writer->writeFile('Hello World!', 'qrcode.png');
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
// Generated by ZF2's ./bin/classmap_generator.php
|
||||
return array(
|
||||
'BaconQrCode\Common\AbstractEnum' => __DIR__ . '/src/BaconQrCode/Common/AbstractEnum.php',
|
||||
'BaconQrCode\Common\BitArray' => __DIR__ . '/src/BaconQrCode/Common/BitArray.php',
|
||||
'BaconQrCode\Common\BitMatrix' => __DIR__ . '/src/BaconQrCode/Common/BitMatrix.php',
|
||||
'BaconQrCode\Common\BitUtils' => __DIR__ . '/src/BaconQrCode/Common/BitUtils.php',
|
||||
'BaconQrCode\Common\CharacterSetEci' => __DIR__ . '/src/BaconQrCode/Common/CharacterSetEci.php',
|
||||
'BaconQrCode\Common\EcBlock' => __DIR__ . '/src/BaconQrCode/Common/EcBlock.php',
|
||||
'BaconQrCode\Common\EcBlocks' => __DIR__ . '/src/BaconQrCode/Common/EcBlocks.php',
|
||||
'BaconQrCode\Common\ErrorCorrectionLevel' => __DIR__ . '/src/BaconQrCode/Common/ErrorCorrectionLevel.php',
|
||||
'BaconQrCode\Common\FormatInformation' => __DIR__ . '/src/BaconQrCode/Common/FormatInformation.php',
|
||||
'BaconQrCode\Common\Mode' => __DIR__ . '/src/BaconQrCode/Common/Mode.php',
|
||||
'BaconQrCode\Common\ReedSolomonCodec' => __DIR__ . '/src/BaconQrCode/Common/ReedSolomonCodec.php',
|
||||
'BaconQrCode\Common\Version' => __DIR__ . '/src/BaconQrCode/Common/Version.php',
|
||||
'BaconQrCode\Encoder\BlockPair' => __DIR__ . '/src/BaconQrCode/Encoder/BlockPair.php',
|
||||
'BaconQrCode\Encoder\ByteMatrix' => __DIR__ . '/src/BaconQrCode/Encoder/ByteMatrix.php',
|
||||
'BaconQrCode\Encoder\Encoder' => __DIR__ . '/src/BaconQrCode/Encoder/Encoder.php',
|
||||
'BaconQrCode\Encoder\MaskUtil' => __DIR__ . '/src/BaconQrCode/Encoder/MaskUtil.php',
|
||||
'BaconQrCode\Encoder\MatrixUtil' => __DIR__ . '/src/BaconQrCode/Encoder/MatrixUtil.php',
|
||||
'BaconQrCode\Encoder\QrCode' => __DIR__ . '/src/BaconQrCode/Encoder/QrCode.php',
|
||||
'BaconQrCode\Exception\ExceptionInterface' => __DIR__ . '/src/BaconQrCode/Exception/ExceptionInterface.php',
|
||||
'BaconQrCode\Exception\InvalidArgumentException' => __DIR__ . '/src/BaconQrCode/Exception/InvalidArgumentException.php',
|
||||
'BaconQrCode\Exception\OutOfBoundsException' => __DIR__ . '/src/BaconQrCode/Exception/OutOfBoundsException.php',
|
||||
'BaconQrCode\Exception\RuntimeException' => __DIR__ . '/src/BaconQrCode/Exception/RuntimeException.php',
|
||||
'BaconQrCode\Exception\UnexpectedValueException' => __DIR__ . '/src/BaconQrCode/Exception/UnexpectedValueException.php',
|
||||
'BaconQrCode\Exception\WriterException' => __DIR__ . '/src/BaconQrCode/Exception/WriterException.php',
|
||||
'BaconQrCode\Renderer\Color\Cmyk' => __DIR__ . '/src/BaconQrCode/Renderer/Color/Cmyk.php',
|
||||
'BaconQrCode\Renderer\Color\ColorInterface' => __DIR__ . '/src/BaconQrCode/Renderer/Color/ColorInterface.php',
|
||||
'BaconQrCode\Renderer\Color\Gray' => __DIR__ . '/src/BaconQrCode/Renderer/Color/Gray.php',
|
||||
'BaconQrCode\Renderer\Color\Rgb' => __DIR__ . '/src/BaconQrCode/Renderer/Color/Rgb.php',
|
||||
'BaconQrCode\Renderer\Image\AbstractRenderer' => __DIR__ . '/src/BaconQrCode/Renderer/Image/AbstractRenderer.php',
|
||||
'BaconQrCode\Renderer\Image\Decorator\DecoratorInterface' => __DIR__ . '/src/BaconQrCode/Renderer/Image/Decorator/DecoratorInterface.php',
|
||||
'BaconQrCode\Renderer\Image\Decorator\FinderPattern' => __DIR__ . '/src/BaconQrCode/Renderer/Image/Decorator/FinderPattern.php',
|
||||
'BaconQrCode\Renderer\Image\Eps' => __DIR__ . '/src/BaconQrCode/Renderer/Image/Eps.php',
|
||||
'BaconQrCode\Renderer\Image\Png' => __DIR__ . '/src/BaconQrCode/Renderer/Image/Png.php',
|
||||
'BaconQrCode\Renderer\Image\RendererInterface' => __DIR__ . '/src/BaconQrCode/Renderer/Image/RendererInterface.php',
|
||||
'BaconQrCode\Renderer\Image\Svg' => __DIR__ . '/src/BaconQrCode/Renderer/Image/Svg.php',
|
||||
'BaconQrCode\Renderer\RendererInterface' => __DIR__ . '/src/BaconQrCode/Renderer/RendererInterface.php',
|
||||
'BaconQrCode\Renderer\Text\Plain' => __DIR__ . '/src/BaconQrCode/Renderer/Text/Plain.php',
|
||||
'BaconQrCode\Renderer\Text\Html' => __DIR__ . '/src/BaconQrCode/Renderer/Text/Html.php',
|
||||
'BaconQrCode\Writer' => __DIR__ . '/src/BaconQrCode/Writer.php',
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
return function ($class) {
|
||||
static $map;
|
||||
if (!$map) {
|
||||
$map = include __DIR__ . '/autoload_classmap.php';
|
||||
}
|
||||
|
||||
if (!isset($map[$class])) {
|
||||
return false;
|
||||
}
|
||||
return include $map[$class];
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
<?php
|
||||
spl_autoload_register(include __DIR__ . '/autoload_function.php');
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* A general enum implementation until we got SplEnum.
|
||||
*/
|
||||
abstract class AbstractEnum
|
||||
{
|
||||
/**
|
||||
* Default value.
|
||||
*/
|
||||
const __default = null;
|
||||
|
||||
/**
|
||||
* Current value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Cache of constants.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $constants;
|
||||
|
||||
/**
|
||||
* Whether to handle values strict or not.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $strict;
|
||||
|
||||
/**
|
||||
* Creates a new enum.
|
||||
*
|
||||
* @param mixed $initialValue
|
||||
* @param boolean $strict
|
||||
*/
|
||||
public function __construct($initialValue = null, $strict = false)
|
||||
{
|
||||
$this->strict = $strict;
|
||||
$this->change($initialValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the value of the enum.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function change($value)
|
||||
{
|
||||
if (!in_array($value, $this->getConstList(), $this->strict)) {
|
||||
throw new Exception\UnexpectedValueException('Value not a const in enum ' . get_class($this));
|
||||
}
|
||||
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all constants (possible values) as an array.
|
||||
*
|
||||
* @param boolean $includeDefault
|
||||
* @return array
|
||||
*/
|
||||
public function getConstList($includeDefault = true)
|
||||
{
|
||||
if ($this->constants === null) {
|
||||
$reflection = new ReflectionClass($this);
|
||||
$this->constants = $reflection->getConstants();
|
||||
}
|
||||
|
||||
if ($includeDefault) {
|
||||
return $this->constants;
|
||||
}
|
||||
|
||||
$constants = $this->constants;
|
||||
unset($constants['__default']);
|
||||
|
||||
return $constants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the enum.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return array_search($this->value, $this->getConstList());
|
||||
}
|
||||
}
|
||||
+435
@@ -0,0 +1,435 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* A simple, fast array of bits.
|
||||
*/
|
||||
class BitArray
|
||||
{
|
||||
/**
|
||||
* Bits represented as an array of integers.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
protected $bits;
|
||||
|
||||
/**
|
||||
* Size of the bit array in bits.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $size;
|
||||
|
||||
/**
|
||||
* Creates a new bit array with a given size.
|
||||
*
|
||||
* @param integer $size
|
||||
*/
|
||||
public function __construct($size = 0)
|
||||
{
|
||||
$this->size = $size;
|
||||
$this->bits = new SplFixedArray(($this->size + 31) >> 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size in bits.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size in bytes.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getSizeInBytes()
|
||||
{
|
||||
return ($this->size + 7) >> 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the array has a minimum capacity.
|
||||
*
|
||||
* @param integer $size
|
||||
* @return void
|
||||
*/
|
||||
public function ensureCapacity($size)
|
||||
{
|
||||
if ($size > count($this->bits) << 5) {
|
||||
$this->bits->setSize(($size + 31) >> 5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific bit.
|
||||
*
|
||||
* @param integer $i
|
||||
* @return boolean
|
||||
*/
|
||||
public function get($i)
|
||||
{
|
||||
return ($this->bits[$i >> 5] & (1 << ($i & 0x1f))) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific bit.
|
||||
*
|
||||
* @param integer $i
|
||||
* @return void
|
||||
*/
|
||||
public function set($i)
|
||||
{
|
||||
$this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips a specific bit.
|
||||
*
|
||||
* @param integer $i
|
||||
* @return void
|
||||
*/
|
||||
public function flip($i)
|
||||
{
|
||||
$this->bits[$i >> 5] ^= 1 << ($i & 0x1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next set bit position from a given position.
|
||||
*
|
||||
* @param integer $from
|
||||
* @return integer
|
||||
*/
|
||||
public function getNextSet($from)
|
||||
{
|
||||
if ($from >= $this->size) {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
$bitsOffset = $from >> 5;
|
||||
$currentBits = $this->bits[$bitsOffset];
|
||||
$bitsLength = count($this->bits);
|
||||
|
||||
$currentBits &= ~((1 << ($from & 0x1f)) - 1);
|
||||
|
||||
while ($currentBits === 0) {
|
||||
if (++$bitsOffset === $bitsLength) {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
$currentBits = $this->bits[$bitsOffset];
|
||||
}
|
||||
|
||||
$result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
|
||||
|
||||
return $result > $this->size ? $this->size : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next unset bit position from a given position.
|
||||
*
|
||||
* @param integer $from
|
||||
* @return integer
|
||||
*/
|
||||
public function getNextUnset($from)
|
||||
{
|
||||
if ($from >= $this->size) {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
$bitsOffset = $from >> 5;
|
||||
$currentBits = ~$this->bits[$bitsOffset];
|
||||
$bitsLength = count($this->bits);
|
||||
|
||||
$currentBits &= ~((1 << ($from & 0x1f)) - 1);
|
||||
|
||||
while ($currentBits === 0) {
|
||||
if (++$bitsOffset === $bitsLength) {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
$currentBits = ~$this->bits[$bitsOffset];
|
||||
}
|
||||
|
||||
$result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
|
||||
|
||||
return $result > $this->size ? $this->size : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a bulk of bits.
|
||||
*
|
||||
* @param integer $i
|
||||
* @param integer $newBits
|
||||
* @return void
|
||||
*/
|
||||
public function setBulk($i, $newBits)
|
||||
{
|
||||
$this->bits[$i >> 5] = $newBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a range of bits.
|
||||
*
|
||||
* @param integer $start
|
||||
* @param integer $end
|
||||
* @return void
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function setRange($start, $end)
|
||||
{
|
||||
if ($end < $start) {
|
||||
throw new Exception\InvalidArgumentException('End must be greater or equal to start');
|
||||
}
|
||||
|
||||
if ($end === $start) {
|
||||
return;
|
||||
}
|
||||
|
||||
$end--;
|
||||
|
||||
$firstInt = $start >> 5;
|
||||
$lastInt = $end >> 5;
|
||||
|
||||
for ($i = $firstInt; $i <= $lastInt; $i++) {
|
||||
$firstBit = $i > $firstInt ? 0 : $start & 0x1f;
|
||||
$lastBit = $i < $lastInt ? 31 : $end & 0x1f;
|
||||
|
||||
if ($firstBit === 0 && $lastBit === 31) {
|
||||
$mask = 0x7fffffff;
|
||||
} else {
|
||||
$mask = 0;
|
||||
|
||||
for ($j = $firstBit; $j < $lastBit; $j++) {
|
||||
$mask |= 1 << $j;
|
||||
}
|
||||
}
|
||||
|
||||
$this->bits[$i] = $this->bits[$i] | $mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the bit array, unsetting every bit.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$bitsLength = count($this->bits);
|
||||
|
||||
for ($i = 0; $i < $bitsLength; $i++) {
|
||||
$this->bits[$i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a range of bits is set or not set.
|
||||
*
|
||||
* @param integer $start
|
||||
* @param integer $end
|
||||
* @param integer $value
|
||||
* @return boolean
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function isRange($start, $end, $value)
|
||||
{
|
||||
if ($end < $start) {
|
||||
throw new Exception\InvalidArgumentException('End must be greater or equal to start');
|
||||
}
|
||||
|
||||
if ($end === $start) {
|
||||
return;
|
||||
}
|
||||
|
||||
$end--;
|
||||
|
||||
$firstInt = $start >> 5;
|
||||
$lastInt = $end >> 5;
|
||||
|
||||
for ($i = $firstInt; $i <= $lastInt; $i++) {
|
||||
$firstBit = $i > $firstInt ? 0 : $start & 0x1f;
|
||||
$lastBit = $i < $lastInt ? 31 : $end & 0x1f;
|
||||
|
||||
if ($firstBit === 0 && $lastBit === 31) {
|
||||
$mask = 0x7fffffff;
|
||||
} else {
|
||||
$mask = 0;
|
||||
|
||||
for ($j = $firstBit; $j <= $lastBit; $j++) {
|
||||
$mask |= 1 << $j;
|
||||
}
|
||||
}
|
||||
|
||||
if (($this->bits[$i] & $mask) !== ($value ? $mask : 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a bit to the array.
|
||||
*
|
||||
* @param boolean $bit
|
||||
* @return void
|
||||
*/
|
||||
public function appendBit($bit)
|
||||
{
|
||||
$this->ensureCapacity($this->size + 1);
|
||||
|
||||
if ($bit) {
|
||||
$this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f));
|
||||
}
|
||||
|
||||
$this->size++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a number of bits (up to 32) to the array.
|
||||
*
|
||||
* @param integer $value
|
||||
* @param integer $numBits
|
||||
* @return void
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function appendBits($value, $numBits)
|
||||
{
|
||||
if ($numBits < 0 || $numBits > 32) {
|
||||
throw new Exception\InvalidArgumentException('Num bits must be between 0 and 32');
|
||||
}
|
||||
|
||||
$this->ensureCapacity($this->size + $numBits);
|
||||
|
||||
for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) {
|
||||
$this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) === 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends another bit array to this array.
|
||||
*
|
||||
* @param BitArray $other
|
||||
* @return void
|
||||
*/
|
||||
public function appendBitArray(self $other)
|
||||
{
|
||||
$otherSize = $other->getSize();
|
||||
$this->ensureCapacity($this->size + $other->getSize());
|
||||
|
||||
for ($i = 0; $i < $otherSize; $i++) {
|
||||
$this->appendBit($other->get($i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an exclusive-or comparision on the current bit array.
|
||||
*
|
||||
* @param BitArray $other
|
||||
* @return void
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function xorBits(self $other)
|
||||
{
|
||||
$bitsLength = count($this->bits);
|
||||
$otherBits = $other->getBitArray();
|
||||
|
||||
if ($bitsLength !== count($otherBits)) {
|
||||
throw new Exception\InvalidArgumentException('Sizes don\'t match');
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $bitsLength; $i++) {
|
||||
$this->bits[$i] = $this->bits[$i] ^ $otherBits[$i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the bit array to a byte array.
|
||||
*
|
||||
* @param integer $bitOffset
|
||||
* @param integer $numBytes
|
||||
* @return SplFixedArray
|
||||
*/
|
||||
public function toBytes($bitOffset, $numBytes)
|
||||
{
|
||||
$bytes = new SplFixedArray($numBytes);
|
||||
|
||||
for ($i = 0; $i < $numBytes; $i++) {
|
||||
$byte = 0;
|
||||
|
||||
for ($j = 0; $j < 8; $j++) {
|
||||
if ($this->get($bitOffset)) {
|
||||
$byte |= 1 << (7 - $j);
|
||||
}
|
||||
|
||||
$bitOffset++;
|
||||
}
|
||||
|
||||
$bytes[$i] = $byte;
|
||||
}
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the internal bit array.
|
||||
*
|
||||
* @return SplFixedArray
|
||||
*/
|
||||
public function getBitArray()
|
||||
{
|
||||
return $this->bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the array.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reverse()
|
||||
{
|
||||
$newBits = new SplFixedArray(count($this->bits));
|
||||
|
||||
for ($i = 0; $i < $this->size; $i++) {
|
||||
if ($this->get($this->size - $i - 1)) {
|
||||
$newBits[$i >> 5] = $newBits[$i >> 5] | (1 << ($i & 0x1f));
|
||||
}
|
||||
}
|
||||
|
||||
$this->bits = newBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the bit array.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$result = '';
|
||||
|
||||
for ($i = 0; $i < $this->size; $i++) {
|
||||
if (($i & 0x07) === 0) {
|
||||
$result .= ' ';
|
||||
}
|
||||
|
||||
$result .= $this->get($i) ? 'X' : '.';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Bit matrix.
|
||||
*
|
||||
* Represents a 2D matrix of bits. In function arguments below, and throughout
|
||||
* the common module, x is the column position, and y is the row position. The
|
||||
* ordering is always x, y. The origin is at the top-left.
|
||||
*/
|
||||
class BitMatrix
|
||||
{
|
||||
/**
|
||||
* Width of the bit matrix.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $width;
|
||||
|
||||
/**
|
||||
* Height of the bit matrix.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $height;
|
||||
|
||||
/**
|
||||
* Size in bits of each individual row.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $rowSize;
|
||||
|
||||
/**
|
||||
* Bits representation.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
protected $bits;
|
||||
|
||||
/**
|
||||
* Creates a new bit matrix with given dimensions.
|
||||
*
|
||||
* @param integer $width
|
||||
* @param integer|null $height
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function __construct($width, $height = null)
|
||||
{
|
||||
if ($height === null) {
|
||||
$height = $width;
|
||||
}
|
||||
|
||||
if ($width < 1 || $height < 1) {
|
||||
throw new Exception\InvalidArgumentException('Both dimensions must be greater than zero');
|
||||
}
|
||||
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
$this->rowSize = ($width + 31) >> 5;
|
||||
$this->bits = new SplFixedArray($this->rowSize * $height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the requested bit, where true means black.
|
||||
*
|
||||
* @param integer $x
|
||||
* @param integer $y
|
||||
* @return boolean
|
||||
*/
|
||||
public function get($x, $y)
|
||||
{
|
||||
$offset = $y * $this->rowSize + ($x >> 5);
|
||||
return (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given bit to true.
|
||||
*
|
||||
* @param integer $x
|
||||
* @param integer $y
|
||||
* @return void
|
||||
*/
|
||||
public function set($x, $y)
|
||||
{
|
||||
$offset = $y * $this->rowSize + ($x >> 5);
|
||||
$this->bits[$offset] = $this->bits[$offset] | (1 << ($x & 0x1f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips the given bit.
|
||||
*
|
||||
* @param integer $x
|
||||
* @param integer $y
|
||||
* @return void
|
||||
*/
|
||||
public function flip($x, $y)
|
||||
{
|
||||
$offset = $y * $this->rowSize + ($x >> 5);
|
||||
$this->bits[$offset] = $this->bits[$offset] ^ (1 << ($x & 0x1f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all bits (set to false).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$max = count($this->bits);
|
||||
|
||||
for ($i = 0; $i < $max; $i++) {
|
||||
$this->bits[$i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a square region of the bit matrix to true.
|
||||
*
|
||||
* @param integer $left
|
||||
* @param integer $top
|
||||
* @param integer $width
|
||||
* @param integer $height
|
||||
* @return void
|
||||
*/
|
||||
public function setRegion($left, $top, $width, $height)
|
||||
{
|
||||
if ($top < 0 || $left < 0) {
|
||||
throw new Exception\InvalidArgumentException('Left and top must be non-negative');
|
||||
}
|
||||
|
||||
if ($height < 1 || $width < 1) {
|
||||
throw new Exception\InvalidArgumentException('Width and height must be at least 1');
|
||||
}
|
||||
|
||||
$right = $left + $width;
|
||||
$bottom = $top + $height;
|
||||
|
||||
if ($bottom > $this->height || $right > $this->width) {
|
||||
throw new Exception\InvalidArgumentException('The region must fit inside the matrix');
|
||||
}
|
||||
|
||||
for ($y = $top; $y < $bottom; $y++) {
|
||||
$offset = $y * $this->rowSize;
|
||||
|
||||
for ($x = $left; $x < $right; $x++) {
|
||||
$index = $offset + ($x >> 5);
|
||||
$this->bits[$index] = $this->bits[$index] | (1 << ($x & 0x1f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fast method to retrieve one row of data from the matrix as a BitArray.
|
||||
*
|
||||
* @param integer $y
|
||||
* @param BitArray $row
|
||||
* @return BitArray
|
||||
*/
|
||||
public function getRow($y, BitArray $row = null)
|
||||
{
|
||||
if ($row === null || $row->getSize() < $this->width) {
|
||||
$row = new BitArray($this->width);
|
||||
}
|
||||
|
||||
$offset = $y * $this->rowSize;
|
||||
|
||||
for ($x = 0; $x < $this->rowSize; $x++) {
|
||||
$row->setBulk($x << 5, $this->bits[$offset + $x]);
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a row of data from a BitArray.
|
||||
*
|
||||
* @param integer $y
|
||||
* @param BitArray $row
|
||||
* @return void
|
||||
*/
|
||||
public function setRow($y, BitArray $row)
|
||||
{
|
||||
$bits = $row->getBitArray();
|
||||
|
||||
for ($i = 0; $i < $this->rowSize; $i++) {
|
||||
$this->bits[$y * $this->rowSize + $i] = $bits[$i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is useful in detecting the enclosing rectangle of a 'pure' barcode.
|
||||
*
|
||||
* @return SplFixedArray
|
||||
*/
|
||||
public function getEnclosingRectangle()
|
||||
{
|
||||
$left = $this->width;
|
||||
$top = $this->height;
|
||||
$right = -1;
|
||||
$bottom = -1;
|
||||
|
||||
for ($y = 0; $y < $this->height; $y++) {
|
||||
for ($x32 = 0; $x32 < $this->rowSize; $x32++) {
|
||||
$bits = $this->bits[$y * $this->rowSize + $x32];
|
||||
|
||||
if ($bits !== 0) {
|
||||
if ($y < $top) {
|
||||
$top = $y;
|
||||
}
|
||||
|
||||
if ($y > $bottom) {
|
||||
$bottom = $y;
|
||||
}
|
||||
|
||||
if ($x32 * 32 < $left) {
|
||||
$bit = 0;
|
||||
|
||||
while (($bits << (31 - $bit)) === 0) {
|
||||
$bit++;
|
||||
}
|
||||
|
||||
if (($x32 * 32 + $bit) < $left) {
|
||||
$left = $x32 * 32 + $bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($x32 * 32 + 31 > $right) {
|
||||
$bit = 31;
|
||||
|
||||
while (BitUtils::unsignedRightShift($bits, $bit) === 0) {
|
||||
$bit--;
|
||||
}
|
||||
|
||||
if (($x32 * 32 + $bit) > $right) {
|
||||
$right = $x32 * 32 + $bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$width = $right - $left;
|
||||
$height = $bottom - $top;
|
||||
|
||||
if ($width < 0 || $height < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return SplFixedArray::fromArray(array($left, $top, $width, $height), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most top left set bit.
|
||||
*
|
||||
* This is useful in detecting a corner of a 'pure' barcode.
|
||||
*
|
||||
* @return SplFixedArray
|
||||
*/
|
||||
public function getTopLeftOnBit()
|
||||
{
|
||||
$bitsOffset = 0;
|
||||
|
||||
while ($bitsOffset < count($this->bits) && $this->bits[$bitsOffset] === 0) {
|
||||
$bitsOffset++;
|
||||
}
|
||||
|
||||
if ($bitsOffset === count($this->bits)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$x = intval($bitsOffset / $this->rowSize);
|
||||
$y = ($bitsOffset % $this->rowSize) << 5;
|
||||
|
||||
$bits = $this->bits[$bitsOffset];
|
||||
$bit = 0;
|
||||
|
||||
while (($bits << (31 - $bit)) === 0) {
|
||||
$bit++;
|
||||
}
|
||||
|
||||
$x += $bit;
|
||||
|
||||
return SplFixedArray::fromArray(array($x, $y), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most bottom right set bit.
|
||||
*
|
||||
* This is useful in detecting a corner of a 'pure' barcode.
|
||||
*
|
||||
* @return SplFixedArray
|
||||
*/
|
||||
public function getBottomRightOnBit()
|
||||
{
|
||||
$bitsOffset = count($this->bits) - 1;
|
||||
|
||||
while ($bitsOffset >= 0 && $this->bits[$bitsOffset] === 0) {
|
||||
$bitsOffset--;
|
||||
}
|
||||
|
||||
if ($bitsOffset < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$x = intval($bitsOffset / $this->rowSize);
|
||||
$y = ($bitsOffset % $this->rowSize) << 5;
|
||||
|
||||
$bits = $this->bits[$bitsOffset];
|
||||
$bit = 0;
|
||||
|
||||
while (BitUtils::unsignedRightShift($bits, $bit) === 0) {
|
||||
$bit--;
|
||||
}
|
||||
|
||||
$x += $bit;
|
||||
|
||||
return SplFixedArray::fromArray(array($x, $y), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of the matrix,
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height of the matrix.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
/**
|
||||
* General bit utilities.
|
||||
*
|
||||
* All utility methods are based on 32-bit integers and also work on 64-bit
|
||||
* systems.
|
||||
*/
|
||||
class BitUtils
|
||||
{
|
||||
/**
|
||||
* Performs an unsigned right shift.
|
||||
*
|
||||
* This is the same as the unsigned right shift operator ">>>" in other
|
||||
* languages.
|
||||
*
|
||||
* @param integer $a
|
||||
* @param integer $b
|
||||
* @return integer
|
||||
*/
|
||||
public static function unsignedRightShift($a, $b)
|
||||
{
|
||||
return (
|
||||
$a >= 0
|
||||
? $a >> $b
|
||||
: (($a & 0x7fffffff) >> $b) | (0x40000000 >> ($b - 1))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of trailing zeros.
|
||||
*
|
||||
* @param integer $i
|
||||
* @return integer
|
||||
*/
|
||||
public static function numberOfTrailingZeros($i)
|
||||
{
|
||||
$lastPos = strrpos(str_pad(decbin($i), 32, '0', STR_PAD_LEFT), '1');
|
||||
|
||||
return $lastPos === false ? 32 : 31 - $lastPos;
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
/**
|
||||
* Encapsulates a Character Set ECI, according to "Extended Channel
|
||||
* Interpretations" 5.3.1.1 of ISO 18004.
|
||||
*/
|
||||
class CharacterSetEci extends AbstractEnum
|
||||
{
|
||||
/**#@+
|
||||
* Character set constants.
|
||||
*/
|
||||
const CP437 = 0;
|
||||
const ISO8859_1 = 1;
|
||||
const ISO8859_2 = 4;
|
||||
const ISO8859_3 = 5;
|
||||
const ISO8859_4 = 6;
|
||||
const ISO8859_5 = 7;
|
||||
const ISO8859_6 = 8;
|
||||
const ISO8859_7 = 9;
|
||||
const ISO8859_8 = 10;
|
||||
const ISO8859_9 = 11;
|
||||
const ISO8859_10 = 12;
|
||||
const ISO8859_11 = 13;
|
||||
const ISO8859_12 = 14;
|
||||
const ISO8859_13 = 15;
|
||||
const ISO8859_14 = 16;
|
||||
const ISO8859_15 = 17;
|
||||
const ISO8859_16 = 18;
|
||||
const SJIS = 20;
|
||||
const CP1250 = 21;
|
||||
const CP1251 = 22;
|
||||
const CP1252 = 23;
|
||||
const CP1256 = 24;
|
||||
const UNICODE_BIG_UNMARKED = 25;
|
||||
const UTF8 = 26;
|
||||
const ASCII = 27;
|
||||
const BIG5 = 28;
|
||||
const GB18030 = 29;
|
||||
const EUC_KR = 30;
|
||||
/**#@-*/
|
||||
|
||||
/**
|
||||
* Map between character names and their ECI values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $nameToEci = array(
|
||||
'ISO-8859-1' => self::ISO8859_1,
|
||||
'ISO-8859-2' => self::ISO8859_2,
|
||||
'ISO-8859-3' => self::ISO8859_3,
|
||||
'ISO-8859-4' => self::ISO8859_4,
|
||||
'ISO-8859-5' => self::ISO8859_5,
|
||||
'ISO-8859-6' => self::ISO8859_6,
|
||||
'ISO-8859-7' => self::ISO8859_7,
|
||||
'ISO-8859-8' => self::ISO8859_8,
|
||||
'ISO-8859-9' => self::ISO8859_9,
|
||||
'ISO-8859-10' => self::ISO8859_10,
|
||||
'ISO-8859-11' => self::ISO8859_11,
|
||||
'ISO-8859-12' => self::ISO8859_12,
|
||||
'ISO-8859-13' => self::ISO8859_13,
|
||||
'ISO-8859-14' => self::ISO8859_14,
|
||||
'ISO-8859-15' => self::ISO8859_15,
|
||||
'ISO-8859-16' => self::ISO8859_16,
|
||||
'SHIFT-JIS' => self::SJIS,
|
||||
'WINDOWS-1250' => self::CP1250,
|
||||
'WINDOWS-1251' => self::CP1251,
|
||||
'WINDOWS-1252' => self::CP1252,
|
||||
'WINDOWS-1256' => self::CP1256,
|
||||
'UTF-16BE' => self::UNICODE_BIG_UNMARKED,
|
||||
'UTF-8' => self::UTF8,
|
||||
'ASCII' => self::ASCII,
|
||||
'GBK' => self::GB18030,
|
||||
'EUC-KR' => self::EUC_KR,
|
||||
);
|
||||
|
||||
/**
|
||||
* Additional possible values for character sets.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $additionalValues = array(
|
||||
self::CP437 => 2,
|
||||
self::ASCII => 170,
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets character set ECI by value.
|
||||
*
|
||||
* @param string $name
|
||||
* @return CharacterSetEci|null
|
||||
*/
|
||||
public static function getCharacterSetECIByValue($value)
|
||||
{
|
||||
if ($value < 0 || $value >= 900) {
|
||||
throw new Exception\InvalidArgumentException('Value must be between 0 and 900');
|
||||
}
|
||||
|
||||
if (false !== ($key = array_search($value, self::$additionalValues))) {
|
||||
$value = $key;
|
||||
}
|
||||
|
||||
try {
|
||||
return new self($value);
|
||||
} catch (Exception\UnexpectedValueException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets character set ECI by name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return CharacterSetEci|null
|
||||
*/
|
||||
public static function getCharacterSetECIByName($name)
|
||||
{
|
||||
$name = strtoupper($name);
|
||||
|
||||
if (isset(self::$nameToEci[$name])) {
|
||||
return new self(self::$nameToEci[$name]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
/**
|
||||
* Encapsualtes the parameters for one error-correction block in one symbol
|
||||
* version. This includes the number of data codewords, and the number of times
|
||||
* a block with these parameters is used consecutively in the QR code version's
|
||||
* format.
|
||||
*/
|
||||
class EcBlock
|
||||
{
|
||||
/**
|
||||
* How many times the block is used.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $count;
|
||||
|
||||
/**
|
||||
* Number of data codewords.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $dataCodewords;
|
||||
|
||||
/**
|
||||
* Creates a new EC block.
|
||||
*
|
||||
* @param integer $count
|
||||
* @param integer $dataCodewords
|
||||
*/
|
||||
public function __construct($count, $dataCodewords)
|
||||
{
|
||||
$this->count = $count;
|
||||
$this->dataCodewords = $dataCodewords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many times the block is used.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getCount()
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of data codewords.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getDataCodewords()
|
||||
{
|
||||
return $this->dataCodewords;
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Encapsulates a set of error-correction blocks in one symbol version. Most
|
||||
* versions will use blocks of differing sizes within one version, so, this
|
||||
* encapsulates the parameters for each set of blocks. It also holds the number
|
||||
* of error-correction codewords per block since it will be the same across all
|
||||
* blocks within one version.
|
||||
*/
|
||||
class EcBlocks
|
||||
{
|
||||
/**
|
||||
* Number of EC codewords per block.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $ecCodewordsPerBlock;
|
||||
|
||||
/**
|
||||
* List of EC blocks.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
protected $ecBlocks;
|
||||
|
||||
/**
|
||||
* Creates a new EC blocks instance.
|
||||
*
|
||||
* @param integer $ecCodewordsPerBlock
|
||||
* @param EcBlock $ecb1
|
||||
* @param EcBlock|null $ecb2
|
||||
*/
|
||||
public function __construct($ecCodewordsPerBlock, EcBlock $ecb1, EcBlock $ecb2 = null)
|
||||
{
|
||||
$this->ecCodewordsPerBlock = $ecCodewordsPerBlock;
|
||||
|
||||
$this->ecBlocks = new SplFixedArray($ecb2 === null ? 1 : 2);
|
||||
$this->ecBlocks[0] = $ecb1;
|
||||
|
||||
if ($ecb2 !== null) {
|
||||
$this->ecBlocks[1] = $ecb2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of EC codewords per block.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getEcCodewordsPerBlock()
|
||||
{
|
||||
return $this->ecCodewordsPerBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of EC block appearances.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getNumBlocks()
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
foreach ($this->ecBlocks as $ecBlock) {
|
||||
$total += $ecBlock->getCount();
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total count of EC codewords.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTotalEcCodewords()
|
||||
{
|
||||
return $this->ecCodewordsPerBlock * $this->getNumBlocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the EC blocks included in this collection.
|
||||
*
|
||||
* @return SplFixedArray
|
||||
*/
|
||||
public function getEcBlocks()
|
||||
{
|
||||
return $this->ecBlocks;
|
||||
}
|
||||
}
|
||||
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
/**
|
||||
* Enum representing the four error correction levels.
|
||||
*/
|
||||
class ErrorCorrectionLevel extends AbstractEnum
|
||||
{
|
||||
/**
|
||||
* Level L, ~7% correction.
|
||||
*/
|
||||
const L = 0x1;
|
||||
|
||||
/**
|
||||
* Level M, ~15% correction.
|
||||
*/
|
||||
const M = 0x0;
|
||||
|
||||
/**
|
||||
* Level Q, ~25% correction.
|
||||
*/
|
||||
const Q = 0x3;
|
||||
|
||||
/**
|
||||
* Level H, ~30% correction.
|
||||
*/
|
||||
const H = 0x2;
|
||||
|
||||
/**
|
||||
* Gets the ordinal of this enumeration constant.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getOrdinal()
|
||||
{
|
||||
switch ($this->value) {
|
||||
case self::L:
|
||||
return 0;
|
||||
break;
|
||||
|
||||
case self::M:
|
||||
return 1;
|
||||
break;
|
||||
|
||||
case self::Q:
|
||||
return 2;
|
||||
break;
|
||||
|
||||
case self::H:
|
||||
return 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
/**
|
||||
* Encapsulates a QR Code's format information, including the data mask used and
|
||||
* error correction level.
|
||||
*/
|
||||
class FormatInformation
|
||||
{
|
||||
/**
|
||||
* Mask for format information.
|
||||
*/
|
||||
const FORMAT_INFO_MASK_QR = 0x5412;
|
||||
|
||||
/**
|
||||
* Lookup table for decoding format information.
|
||||
*
|
||||
* See ISO 18004:2006, Annex C, Table C.1
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $formatInfoDecodeLookup = array(
|
||||
array(0x5412, 0x00),
|
||||
array(0x5125, 0x01),
|
||||
array(0x5e7c, 0x02),
|
||||
array(0x5b4b, 0x03),
|
||||
array(0x45f9, 0x04),
|
||||
array(0x40ce, 0x05),
|
||||
array(0x4f97, 0x06),
|
||||
array(0x4aa0, 0x07),
|
||||
array(0x77c4, 0x08),
|
||||
array(0x72f3, 0x09),
|
||||
array(0x7daa, 0x0a),
|
||||
array(0x789d, 0x0b),
|
||||
array(0x662f, 0x0c),
|
||||
array(0x6318, 0x0d),
|
||||
array(0x6c41, 0x0e),
|
||||
array(0x6976, 0x0f),
|
||||
array(0x1689, 0x10),
|
||||
array(0x13be, 0x11),
|
||||
array(0x1ce7, 0x12),
|
||||
array(0x19d0, 0x13),
|
||||
array(0x0762, 0x14),
|
||||
array(0x0255, 0x15),
|
||||
array(0x0d0c, 0x16),
|
||||
array(0x083b, 0x17),
|
||||
array(0x355f, 0x18),
|
||||
array(0x3068, 0x19),
|
||||
array(0x3f31, 0x1a),
|
||||
array(0x3a06, 0x1b),
|
||||
array(0x24b4, 0x1c),
|
||||
array(0x2183, 0x1d),
|
||||
array(0x2eda, 0x1e),
|
||||
array(0x2bed, 0x1f),
|
||||
);
|
||||
|
||||
/**
|
||||
* Offset i holds the number of 1 bits in the binary representation of i.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $bitsSetInHalfByte = array(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);
|
||||
|
||||
/**
|
||||
* Error correction level.
|
||||
*
|
||||
* @var ErrorCorrectionLevel
|
||||
*/
|
||||
protected $ecLevel;
|
||||
|
||||
/**
|
||||
* Data mask.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $dataMask;
|
||||
|
||||
/**
|
||||
* Creates a new format information instance.
|
||||
*
|
||||
* @param integer $formatInfo
|
||||
*/
|
||||
protected function __construct($formatInfo)
|
||||
{
|
||||
$this->ecLevel = new ErrorCorrectionLevel(($formatInfo >> 3) & 0x3);
|
||||
$this->dataMask = $formatInfo & 0x7;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks how many bits are different between two integers.
|
||||
*
|
||||
* @param integer $a
|
||||
* @param integer $b
|
||||
* @return integer
|
||||
*/
|
||||
public static function numBitsDiffering($a, $b)
|
||||
{
|
||||
$a ^= $b;
|
||||
|
||||
return (
|
||||
self::$bitsSetInHalfByte[$a & 0xf]
|
||||
+ self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 4) & 0xf)]
|
||||
+ self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 8) & 0xf)]
|
||||
+ self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 12) & 0xf)]
|
||||
+ self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 16) & 0xf)]
|
||||
+ self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 20) & 0xf)]
|
||||
+ self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 24) & 0xf)]
|
||||
+ self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 28) & 0xf)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes format information.
|
||||
*
|
||||
* @param integer $maskedFormatInfo1
|
||||
* @param integer $maskedFormatInfo2
|
||||
* @return FormatInformation|null
|
||||
*/
|
||||
public static function decodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2)
|
||||
{
|
||||
$formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2);
|
||||
|
||||
if ($formatInfo !== null) {
|
||||
return $formatInfo;
|
||||
}
|
||||
|
||||
// Should return null, but, some QR codes apparently do not mask this
|
||||
// info. Try again by actually masking the pattern first.
|
||||
return self::doDecodeFormatInformation(
|
||||
$maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR,
|
||||
$maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method for decoding format information.
|
||||
*
|
||||
* @param integer $maskedFormatInfo1
|
||||
* @param integer $maskedFormatInfo2
|
||||
* @return FormatInformation|null
|
||||
*/
|
||||
protected static function doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2)
|
||||
{
|
||||
$bestDifference = PHP_INT_MAX;
|
||||
$bestFormatInfo = 0;
|
||||
|
||||
foreach (self::$formatInfoDecodeLookup as $decodeInfo) {
|
||||
$targetInfo = $decodeInfo[0];
|
||||
|
||||
if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) {
|
||||
// Found an exact match
|
||||
return new self($decodeInfo[1]);
|
||||
}
|
||||
|
||||
$bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo);
|
||||
|
||||
if ($bitsDifference < $bestDifference) {
|
||||
$bestFormatInfo = $decodeInfo[1];
|
||||
$bestDifference = $bitsDifference;
|
||||
}
|
||||
|
||||
if ($maskedFormatInfo1 !== $maskedFormatInfo2) {
|
||||
// Also try the other option
|
||||
$bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo);
|
||||
|
||||
if ($bitsDifference < $bestDifference) {
|
||||
$bestFormatInfo = $decodeInfo[1];
|
||||
$bestDifference = $bitsDifference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hamming distance of the 32 masked codes is 7, by construction, so
|
||||
// <= 3 bits differing means we found a match.
|
||||
if ($bestDifference <= 3) {
|
||||
return new self($bestFormatInfo);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error correction level.
|
||||
*
|
||||
* @return ErrorCorrectionLevel
|
||||
*/
|
||||
public function getErrorCorrectionLevel()
|
||||
{
|
||||
return $this->ecLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data mask.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getDataMask()
|
||||
{
|
||||
return $this->dataMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes the code of the EC level.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function hashCode()
|
||||
{
|
||||
return ($this->ecLevel->get() << 3) | $this->dataMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if this instance equals another one.
|
||||
*
|
||||
* @param mixed $other
|
||||
* @return boolean
|
||||
*/
|
||||
public function equals($other) {
|
||||
if (!$other instanceof self) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
$this->ecLevel->get() === $other->getErrorCorrectionLevel()->get()
|
||||
&& $this->dataMask === $other->getDataMask()
|
||||
);
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
/**
|
||||
* Enum representing various modes in which data can be encoded to bits.
|
||||
*/
|
||||
class Mode extends AbstractEnum
|
||||
{
|
||||
/**#@+
|
||||
* Mode constants.
|
||||
*/
|
||||
const TERMINATOR = 0x0;
|
||||
const NUMERIC = 0x1;
|
||||
const ALPHANUMERIC = 0x2;
|
||||
const STRUCTURED_APPEND = 0x3;
|
||||
const BYTE = 0x4;
|
||||
const ECI = 0x7;
|
||||
const KANJI = 0x8;
|
||||
const FNC1_FIRST_POSITION = 0x5;
|
||||
const FNC1_SECOND_POSITION = 0x9;
|
||||
const HANZI = 0xd;
|
||||
/**#@-*/
|
||||
|
||||
/**
|
||||
* Character count bits for each version.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $characterCountBitsForVersions = array(
|
||||
self::TERMINATOR => array(0, 0, 0),
|
||||
self::NUMERIC => array(10, 12, 14),
|
||||
self::ALPHANUMERIC => array(9, 11, 13),
|
||||
self::STRUCTURED_APPEND => array(0, 0, 0),
|
||||
self::BYTE => array(8, 16, 16),
|
||||
self::ECI => array(0, 0, 0),
|
||||
self::KANJI => array(8, 10, 12),
|
||||
self::FNC1_FIRST_POSITION => array(0, 0, 0),
|
||||
self::FNC1_SECOND_POSITION => array(0, 0, 0),
|
||||
self::HANZI => array(8, 10, 12),
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the number of bits used in a specific QR code version.
|
||||
*
|
||||
* @param Version $version
|
||||
* @return integer
|
||||
*/
|
||||
public function getCharacterCountBits(Version $version)
|
||||
{
|
||||
$number = $version->getVersionNumber();
|
||||
|
||||
if ($number <= 9) {
|
||||
$offset = 0;
|
||||
} elseif ($number <= 26) {
|
||||
$offset = 1;
|
||||
} else {
|
||||
$offset = 2;
|
||||
}
|
||||
|
||||
return self::$characterCountBitsForVersions[$this->value][$offset];
|
||||
}
|
||||
}
|
||||
+476
@@ -0,0 +1,476 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Reed-Solomon codec for 8-bit characters.
|
||||
*
|
||||
* Based on libfec by Phil Karn, KA9Q.
|
||||
*/
|
||||
class ReedSolomonCodec
|
||||
{
|
||||
/**
|
||||
* Symbol size in bits.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $symbolSize;
|
||||
|
||||
/**
|
||||
* Block size in symbols.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $blockSize;
|
||||
|
||||
/**
|
||||
* First root of RS code generator polynomial, index form.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $firstRoot;
|
||||
|
||||
/**
|
||||
* Primitive element to generate polynomial roots, index form.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $primitive;
|
||||
|
||||
/**
|
||||
* Prim-th root of 1, index form.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $iPrimitive;
|
||||
|
||||
/**
|
||||
* RS code generator polynomial degree (number of roots).
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $numRoots;
|
||||
|
||||
/**
|
||||
* Padding bytes at front of shortened block.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $padding;
|
||||
|
||||
/**
|
||||
* Log lookup table.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
protected $alphaTo;
|
||||
|
||||
/**
|
||||
* Anti-Log lookup table.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
protected $indexOf;
|
||||
|
||||
/**
|
||||
* Generator polynomial.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
protected $generatorPoly;
|
||||
|
||||
/**
|
||||
* Creates a new reed solomon instance.
|
||||
*
|
||||
* @param integer $symbolSize
|
||||
* @param integer $gfPoly
|
||||
* @param integer $firstRoot
|
||||
* @param integer $primitive
|
||||
* @param integer $numRoots
|
||||
* @param integer $padding
|
||||
* @throws Exception\InvalidArgumentException
|
||||
* @throws Exception\RuntimeException
|
||||
*/
|
||||
public function __construct($symbolSize, $gfPoly, $firstRoot, $primitive, $numRoots, $padding)
|
||||
{
|
||||
if ($symbolSize < 0 || $symbolSize > 8) {
|
||||
throw new Exception\InvalidArgumentException('Symbol size must be between 0 and 8');
|
||||
}
|
||||
|
||||
if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) {
|
||||
throw new Exception\InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize));
|
||||
}
|
||||
|
||||
if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) {
|
||||
throw new Exception\InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize));
|
||||
}
|
||||
|
||||
if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) {
|
||||
throw new Exception\InvalidArgumentException('Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots));
|
||||
}
|
||||
|
||||
$this->symbolSize = $symbolSize;
|
||||
$this->blockSize = (1 << $symbolSize) - 1;
|
||||
$this->padding = $padding;
|
||||
$this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
|
||||
$this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
|
||||
|
||||
// Generate galous field lookup table
|
||||
$this->indexOf[0] = $this->blockSize;
|
||||
$this->alphaTo[$this->blockSize] = 0;
|
||||
|
||||
$sr = 1;
|
||||
|
||||
for ($i = 0; $i < $this->blockSize; $i++) {
|
||||
$this->indexOf[$sr] = $i;
|
||||
$this->alphaTo[$i] = $sr;
|
||||
|
||||
$sr <<= 1;
|
||||
|
||||
if ($sr & (1 << $symbolSize)) {
|
||||
$sr ^= $gfPoly;
|
||||
}
|
||||
|
||||
$sr &= $this->blockSize;
|
||||
}
|
||||
|
||||
if ($sr !== 1) {
|
||||
throw new Exception\RuntimeException('Field generator polynomial is not primitive');
|
||||
}
|
||||
|
||||
// Form RS code generator polynomial from its roots
|
||||
$this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false);
|
||||
$this->firstRoot = $firstRoot;
|
||||
$this->primitive = $primitive;
|
||||
$this->numRoots = $numRoots;
|
||||
|
||||
// Find prim-th root of 1, used in decoding
|
||||
for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize);
|
||||
$this->iPrimitive = intval($iPrimitive / $primitive);
|
||||
|
||||
$this->generatorPoly[0] = 1;
|
||||
|
||||
for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; $i++, $root += $primitive) {
|
||||
$this->generatorPoly[$i + 1] = 1;
|
||||
|
||||
for ($j = $i; $j > 0; $j--) {
|
||||
if ($this->generatorPoly[$j] !== 0) {
|
||||
$this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root)];
|
||||
} else {
|
||||
$this->generatorPoly[$j] = $this->generatorPoly[$j - 1];
|
||||
}
|
||||
}
|
||||
|
||||
$this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)];
|
||||
}
|
||||
|
||||
// Convert generator poly to index form for quicker encoding
|
||||
for ($i = 0; $i <= $numRoots; $i++) {
|
||||
$this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes data and writes result back into parity array.
|
||||
*
|
||||
* @param SplFixedArray $data
|
||||
* @param SplFixedArray $parity
|
||||
* @return void
|
||||
*/
|
||||
public function encode(SplFixedArray $data, SplFixedArray $parity)
|
||||
{
|
||||
for ($i = 0; $i < $this->numRoots; $i++) {
|
||||
$parity[$i] = 0;
|
||||
}
|
||||
|
||||
$iterations = $this->blockSize - $this->numRoots - $this->padding;
|
||||
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
$feedback = $this->indexOf[$data[$i] ^ $parity[0]];
|
||||
|
||||
if ($feedback !== $this->blockSize) {
|
||||
// Feedback term is non-zero
|
||||
$feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback);
|
||||
|
||||
for ($j = 1; $j < $this->numRoots; $j++) {
|
||||
$parity[$j] = $parity[$j] ^ $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j])];
|
||||
}
|
||||
}
|
||||
|
||||
for ($j = 0; $j < $this->numRoots - 1; $j++) {
|
||||
$parity[$j] = $parity[$j + 1];
|
||||
}
|
||||
|
||||
if ($feedback !== $this->blockSize) {
|
||||
$parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])];
|
||||
} else {
|
||||
$parity[$this->numRoots - 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes received data.
|
||||
*
|
||||
* @param SplFixedArray $data
|
||||
* @param SplFixedArray|null $erasures
|
||||
* @return null|integer
|
||||
*/
|
||||
public function decode(SplFixedArray $data, SplFixedArray $erasures = null)
|
||||
{
|
||||
// This speeds up the initialization a bit.
|
||||
$numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false);
|
||||
$numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false);
|
||||
|
||||
$lambda = clone $numRootsPlusOne;
|
||||
$b = clone $numRootsPlusOne;
|
||||
$t = clone $numRootsPlusOne;
|
||||
$omega = clone $numRootsPlusOne;
|
||||
$root = clone $numRoots;
|
||||
$loc = clone $numRoots;
|
||||
|
||||
$numErasures = ($erasures !== null ? count($erasures) : 0);
|
||||
|
||||
// Form the Syndromes; i.e., evaluate data(x) at roots of g(x)
|
||||
$syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false);
|
||||
|
||||
for ($i = 1; $i < $this->blockSize - $this->padding; $i++) {
|
||||
for ($j = 0; $j < $this->numRoots; $j++) {
|
||||
if ($syndromes[$j] === 0) {
|
||||
$syndromes[$j] = $data[$i];
|
||||
} else {
|
||||
$syndromes[$j] = $data[$i] ^ $this->alphaTo[
|
||||
$this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive)
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert syndromes to index form, checking for nonzero conditions
|
||||
$syndromeError = 0;
|
||||
|
||||
for ($i = 0; $i < $this->numRoots; $i++) {
|
||||
$syndromeError |= $syndromes[$i];
|
||||
$syndromes[$i] = $this->indexOf[$syndromes[$i]];
|
||||
}
|
||||
|
||||
if (!$syndromeError) {
|
||||
// If syndrome is zero, data[] is a codeword and there are no errors
|
||||
// to correct, so return data[] unmodified.
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lambda[0] = 1;
|
||||
|
||||
if ($numErasures > 0) {
|
||||
// Init lambda to be the erasure locator polynomial
|
||||
$lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))];
|
||||
|
||||
for ($i = 1; $i < $numErasures; $i++) {
|
||||
$u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i]));
|
||||
|
||||
for ($j = $i + 1; $j > 0; $j--) {
|
||||
$tmp = $this->indexOf[$lambda[$j - 1]];
|
||||
|
||||
if ($tmp !== $this->blockSize) {
|
||||
$lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 0; $i <= $this->numRoots; $i++) {
|
||||
$b[$i] = $this->indexOf[$lambda[$i]];
|
||||
}
|
||||
|
||||
// Begin Berlekamp-Massey algorithm to determine error+erasure locator
|
||||
// polynomial
|
||||
$r = $numErasures;
|
||||
$el = $numErasures;
|
||||
|
||||
while (++$r <= $this->numRoots) {
|
||||
// Compute discrepancy at the r-th step in poly form
|
||||
$discrepancyR = 0;
|
||||
|
||||
for ($i = 0; $i < $r; $i++) {
|
||||
if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) {
|
||||
$discrepancyR ^= $this->alphaTo[$this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1])];
|
||||
}
|
||||
}
|
||||
|
||||
$discrepancyR = $this->indexOf[$discrepancyR];
|
||||
|
||||
if ($discrepancyR === $this->blockSize) {
|
||||
$tmp = $b->toArray();
|
||||
array_unshift($tmp, $this->blockSize);
|
||||
array_pop($tmp);
|
||||
$b = SplFixedArray::fromArray($tmp, false);
|
||||
} else {
|
||||
$t[0] = $lambda[0];
|
||||
|
||||
for ($i = 0; $i < $this->numRoots; $i++) {
|
||||
if ($b[$i] !== $this->blockSize) {
|
||||
$t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])];
|
||||
} else {
|
||||
$t[$i + 1] = $lambda[$i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (2 * $el <= $r + $numErasures - 1) {
|
||||
$el = $r + $numErasures - $el;
|
||||
|
||||
for ($i = 0; $i <= $this->numRoots; $i++) {
|
||||
$b[$i] = (
|
||||
$lambda[$i] === 0
|
||||
? $this->blockSize
|
||||
: $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$tmp = $b->toArray();
|
||||
array_unshift($tmp, $this->blockSize);
|
||||
array_pop($tmp);
|
||||
$b = SplFixedArray::fromArray($tmp, false);
|
||||
}
|
||||
|
||||
$lambda = clone $t;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert lambda to index form and compute deg(lambda(x))
|
||||
$degLambda = 0;
|
||||
|
||||
for ($i = 0; $i <= $this->numRoots; $i++) {
|
||||
$lambda[$i] = $this->indexOf[$lambda[$i]];
|
||||
|
||||
if ($lambda[$i] !== $this->blockSize) {
|
||||
$degLambda = $i;
|
||||
}
|
||||
}
|
||||
|
||||
// Find roots of the error+erasure locator polynomial by Chien search.
|
||||
$reg = clone $lambda;
|
||||
$reg[0] = 0;
|
||||
$count = 0;
|
||||
|
||||
for ($i = 1, $k = $this->iPrimitive - 1; $i <= $this->blockSize; $i++, $k = $this->modNn($k + $this->iPrimitive)) {
|
||||
$q = 1;
|
||||
|
||||
for ($j = $degLambda; $j > 0; $j--) {
|
||||
if ($reg[$j] !== $this->blockSize) {
|
||||
$reg[$j] = $this->modNn($reg[$j] + $j);
|
||||
$q ^= $this->alphaTo[$reg[$j]];
|
||||
}
|
||||
}
|
||||
|
||||
if ($q !== 0) {
|
||||
// Not a root
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store root (index-form) and error location number
|
||||
$root[$count] = $i;
|
||||
$loc[$count] = $k;
|
||||
|
||||
if (++$count === $degLambda) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($degLambda !== $count) {
|
||||
// deg(lambda) unequal to number of roots: uncorreactable error
|
||||
// detected
|
||||
return null;
|
||||
}
|
||||
|
||||
// Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo
|
||||
// x**numRoots). In index form. Also find deg(omega).
|
||||
$degOmega = $degLambda - 1;
|
||||
|
||||
for ($i = 0; $i <= $degOmega; $i++) {
|
||||
$tmp = 0;
|
||||
|
||||
for ($j = $i; $j >= 0; $j--) {
|
||||
if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) {
|
||||
$tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])];
|
||||
}
|
||||
}
|
||||
|
||||
$omega[$i] = $this->indexOf[$tmp];
|
||||
}
|
||||
|
||||
// Compute error values in poly-form. num1 = omega(inv(X(l))), num2 =
|
||||
// inv(X(l))**(firstRoot-1) and den = lambda_pr(inv(X(l))) all in poly
|
||||
// form.
|
||||
for ($j = $count - 1; $j >= 0; $j--) {
|
||||
$num1 = 0;
|
||||
|
||||
for ($i = $degOmega; $i >= 0; $i--) {
|
||||
if ($omega[$i] !== $this->blockSize) {
|
||||
$num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])];
|
||||
}
|
||||
}
|
||||
|
||||
$num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)];
|
||||
$den = 0;
|
||||
|
||||
// lambda[i+1] for i even is the formal derivativelambda_pr of
|
||||
// lambda[i]
|
||||
for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) {
|
||||
if ($lambda[$i + 1] !== $this->blockSize) {
|
||||
$den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])];
|
||||
}
|
||||
}
|
||||
|
||||
// Apply error to data
|
||||
if ($num1 !== 0 && $loc[$j] >= $this->padding) {
|
||||
$data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ (
|
||||
$this->alphaTo[
|
||||
$this->modNn(
|
||||
$this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den]
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($erasures !== null) {
|
||||
if (count($erasures) < $count) {
|
||||
$erasures->setSize($count);
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$erasures[$i] = $loc[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow
|
||||
* divide.
|
||||
*
|
||||
* @param itneger $x
|
||||
* @return integer
|
||||
*/
|
||||
protected function modNn($x)
|
||||
{
|
||||
while ($x >= $this->blockSize) {
|
||||
$x -= $this->blockSize;
|
||||
$x = ($x >> $this->symbolSize) + ($x & $this->blockSize);
|
||||
}
|
||||
|
||||
return $x;
|
||||
}
|
||||
}
|
||||
+687
@@ -0,0 +1,687 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Common;
|
||||
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Version representation.
|
||||
*/
|
||||
class Version
|
||||
{
|
||||
/**
|
||||
* Version decode information.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $versionDecodeInfo = array(
|
||||
0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d,
|
||||
0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9,
|
||||
0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75,
|
||||
0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64,
|
||||
0x27541, 0x28c69
|
||||
);
|
||||
|
||||
/**
|
||||
* Cached version instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $versions = array();
|
||||
|
||||
/**
|
||||
* Version number of this version.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $versionNumber;
|
||||
|
||||
/**
|
||||
* Alignment pattern centers.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
protected $alignmentPatternCenters;
|
||||
|
||||
/**
|
||||
* Error correction blocks.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
protected $errorCorrectionBlocks;
|
||||
|
||||
/**
|
||||
* Total number of codewords.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $totalCodewords;
|
||||
|
||||
/**
|
||||
* Creates a new version.
|
||||
*
|
||||
* @param integer $versionNumber
|
||||
* @param SplFixedArray $alignmentPatternCenters
|
||||
* @param SplFixedArray $ecBlocks
|
||||
*/
|
||||
protected function __construct(
|
||||
$versionNumber,
|
||||
SplFixedArray $alignmentPatternCenters,
|
||||
SplFixedArray $ecBlocks
|
||||
) {
|
||||
$this->versionNumber = $versionNumber;
|
||||
$this->alignmentPatternCenters = $alignmentPatternCenters;
|
||||
$this->errorCorrectionBlocks = $ecBlocks;
|
||||
|
||||
$totalCodewords = 0;
|
||||
$ecCodewords = $ecBlocks[0]->getEcCodewordsPerBlock();
|
||||
|
||||
foreach ($ecBlocks[0]->getEcBlocks() as $ecBlock) {
|
||||
$totalCodewords += $ecBlock->getCount() * ($ecBlock->getDataCodewords() + $ecCodewords);
|
||||
}
|
||||
|
||||
$this->totalCodewords = $totalCodewords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version number.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getVersionNumber()
|
||||
{
|
||||
return $this->versionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the alignment pattern centers.
|
||||
*
|
||||
* @return SplFixedArray
|
||||
*/
|
||||
public function getAlignmentPatternCenters()
|
||||
{
|
||||
return $this->alignmentPatternCenters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of codewords.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTotalCodewords()
|
||||
{
|
||||
return $this->totalCodewords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dimension for the current version.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getDimensionForVersion()
|
||||
{
|
||||
return 17 + 4 * $this->versionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of EC blocks for a specific EC level.
|
||||
*
|
||||
* @param ErrorCorrectionLevel $ecLevel
|
||||
* @return integer
|
||||
*/
|
||||
public function getEcBlocksForLevel(ErrorCorrectionLevel $ecLevel)
|
||||
{
|
||||
return $this->errorCorrectionBlocks[$ecLevel->getOrdinal()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a provisional version number for a specific dimension.
|
||||
*
|
||||
* @param integer $dimension
|
||||
* @return Version
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function getProvisionalVersionForDimension($dimension)
|
||||
{
|
||||
if ($dimension % 4 !== 1) {
|
||||
throw new Exception\InvalidArgumentException('Dimension is not 1 mod 4');
|
||||
}
|
||||
|
||||
return self::getVersionForNumber(($dimension - 17) >> 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a version instance for a specific version number.
|
||||
*
|
||||
* @param integer $versionNumber
|
||||
* @return Version
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function getVersionForNumber($versionNumber)
|
||||
{
|
||||
if ($versionNumber < 1 || $versionNumber > 40) {
|
||||
throw new Exception\InvalidArgumentException('Version number must be between 1 and 40');
|
||||
}
|
||||
|
||||
if (!isset(self::$versions[$versionNumber])) {
|
||||
self::buildVersion($versionNumber);
|
||||
}
|
||||
|
||||
return self::$versions[$versionNumber - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes version information from an integer and returns the version.
|
||||
*
|
||||
* @param integer $versionBits
|
||||
* @return Version|null
|
||||
*/
|
||||
public static function decodeVersionInformation($versionBits)
|
||||
{
|
||||
$bestDifference = PHP_INT_MAX;
|
||||
$bestVersion = 0;
|
||||
|
||||
foreach (self::$versionDecodeInfo as $i => $targetVersion) {
|
||||
if ($targetVersion === $versionBits) {
|
||||
return self::getVersionForNumber($i + 7);
|
||||
}
|
||||
|
||||
$bitsDifference = FormatInformation::numBitsDiffering($versionBits, $targetVersion);
|
||||
|
||||
if ($bitsDifference < $bestDifference) {
|
||||
$bestVersion = $i + 7;
|
||||
$bestDifference = $bitsDifference;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bestDifference <= 3) {
|
||||
return self::getVersionForNumber($bestVersion);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the function pattern for the current version.
|
||||
*
|
||||
* @return BitMatrix
|
||||
*/
|
||||
public function buildFunctionPattern()
|
||||
{
|
||||
$dimension = $this->getDimensionForVersion();
|
||||
$bitMatrix = new BitMatrix($dimension);
|
||||
|
||||
// Top left finder pattern + separator + format
|
||||
$bitMatrix->setRegion(0, 0, 9, 9);
|
||||
// Top right finder pattern + separator + format
|
||||
$bitMatrix->setRegion($dimension - 8, 0, 8, 9);
|
||||
// Bottom left finder pattern + separator + format
|
||||
$bitMatrix->setRegion(0, $dimension - 8, 9, 8);
|
||||
|
||||
// Alignment patterns
|
||||
$max = count($this->alignmentPatternCenters);
|
||||
|
||||
for ($x = 0; $x < $max; $x++) {
|
||||
$i = $this->alignmentPatternCenters[$x] - 2;
|
||||
|
||||
for ($y = 0; $y < $max; $y++) {
|
||||
if (($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)) {
|
||||
// No alignment patterns near the three finder paterns
|
||||
continue;
|
||||
}
|
||||
|
||||
$bitMatrix->setRegion($this->alignmentPatternCenters[$y] - 2, $i, 5, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical timing pattern
|
||||
$bitMatrix->setRegion(6, 9, 1, $dimension - 17);
|
||||
// Horizontal timing pattern
|
||||
$bitMatrix->setRegion(9, 6, $dimension - 17, 1);
|
||||
|
||||
if ($this->versionNumber > 6) {
|
||||
// Version info, top right
|
||||
$bitMatrix->setRegion($dimension - 11, 0, 3, 6);
|
||||
// Version info, bottom left
|
||||
$bitMatrix->setRegion(0, $dimension - 11, 6, 3);
|
||||
}
|
||||
|
||||
return $bitMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation for the version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->versionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and cache a specific version.
|
||||
*
|
||||
* See ISO 18004:2006 6.5.1 Table 9.
|
||||
*
|
||||
* @param integer $versionNumber
|
||||
* @return void
|
||||
*/
|
||||
protected static function buildVersion($versionNumber)
|
||||
{
|
||||
switch ($versionNumber) {
|
||||
case 1:
|
||||
$patterns = array();
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(7, new EcBlock(1, 19)),
|
||||
new EcBlocks(10, new EcBlock(1, 16)),
|
||||
new EcBlocks(13, new EcBlock(1, 13)),
|
||||
new EcBlocks(17, new EcBlock(1, 9)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$patterns = array(6, 18);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(10, new EcBlock(1, 34)),
|
||||
new EcBlocks(16, new EcBlock(1, 28)),
|
||||
new EcBlocks(22, new EcBlock(1, 22)),
|
||||
new EcBlocks(28, new EcBlock(1, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$patterns = array(6, 22);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(15, new EcBlock(1, 55)),
|
||||
new EcBlocks(26, new EcBlock(1, 44)),
|
||||
new EcBlocks(18, new EcBlock(2, 17)),
|
||||
new EcBlocks(22, new EcBlock(2, 13)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
$patterns = array(6, 26);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(20, new EcBlock(1, 80)),
|
||||
new EcBlocks(18, new EcBlock(2, 32)),
|
||||
new EcBlocks(26, new EcBlock(3, 24)),
|
||||
new EcBlocks(16, new EcBlock(4, 9)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
$patterns = array(6, 30);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(26, new EcBlock(1, 108)),
|
||||
new EcBlocks(24, new EcBlock(2, 43)),
|
||||
new EcBlocks(18, new EcBlock(2, 15), new EcBlock(2, 16)),
|
||||
new EcBlocks(22, new EcBlock(2, 11), new EcBlock(2, 12)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
$patterns = array(6, 34);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(18, new EcBlock(2, 68)),
|
||||
new EcBlocks(16, new EcBlock(4, 27)),
|
||||
new EcBlocks(24, new EcBlock(4, 19)),
|
||||
new EcBlocks(28, new EcBlock(4, 15)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 7:
|
||||
$patterns = array(6, 22, 38);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(20, new EcBlock(2, 78)),
|
||||
new EcBlocks(18, new EcBlock(4, 31)),
|
||||
new EcBlocks(18, new EcBlock(2, 14), new EcBlock(4, 15)),
|
||||
new EcBlocks(26, new EcBlock(4, 13)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
$patterns = array(6, 24, 42);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(24, new EcBlock(2, 97)),
|
||||
new EcBlocks(22, new EcBlock(2, 38), new EcBlock(2, 39)),
|
||||
new EcBlocks(22, new EcBlock(4, 18), new EcBlock(2, 19)),
|
||||
new EcBlocks(26, new EcBlock(4, 14), new EcBlock(2, 15)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 9:
|
||||
$patterns = array(6, 26, 46);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(2, 116)),
|
||||
new EcBlocks(22, new EcBlock(3, 36), new EcBlock(2, 37)),
|
||||
new EcBlocks(20, new EcBlock(4, 16), new EcBlock(4, 17)),
|
||||
new EcBlocks(24, new EcBlock(4, 12), new EcBlock(4, 13)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 10:
|
||||
$patterns = array(6, 28, 50);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(18, new EcBlock(2, 68), new EcBlock(2, 69)),
|
||||
new EcBlocks(26, new EcBlock(4, 43), new EcBlock(1, 44)),
|
||||
new EcBlocks(24, new EcBlock(6, 19), new EcBlock(2, 20)),
|
||||
new EcBlocks(28, new EcBlock(6, 15), new EcBlock(2, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 11:
|
||||
$patterns = array(6, 30, 54);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(20, new EcBlock(4, 81)),
|
||||
new EcBlocks(30, new EcBlock(1, 50), new EcBlock(4, 51)),
|
||||
new EcBlocks(28, new EcBlock(4, 22), new EcBlock(4, 23)),
|
||||
new EcBlocks(24, new EcBlock(3, 12), new EcBlock(8, 13)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 12:
|
||||
$patterns = array(6, 32, 58);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(24, new EcBlock(2, 92), new EcBlock(2, 93)),
|
||||
new EcBlocks(22, new EcBlock(6, 36), new EcBlock(2, 37)),
|
||||
new EcBlocks(26, new EcBlock(4, 20), new EcBlock(6, 21)),
|
||||
new EcBlocks(28, new EcBlock(7, 14), new EcBlock(4, 15)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 13:
|
||||
$patterns = array(6, 34, 62);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(26, new EcBlock(4, 107)),
|
||||
new EcBlocks(22, new EcBlock(8, 37), new EcBlock(1, 38)),
|
||||
new EcBlocks(24, new EcBlock(8, 20), new EcBlock(4, 21)),
|
||||
new EcBlocks(22, new EcBlock(12, 11), new EcBlock(4, 12)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 14:
|
||||
$patterns = array(6, 26, 46, 66);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(3, 115), new EcBlock(1, 116)),
|
||||
new EcBlocks(24, new EcBlock(4, 40), new EcBlock(5, 41)),
|
||||
new EcBlocks(20, new EcBlock(11, 16), new EcBlock(5, 17)),
|
||||
new EcBlocks(24, new EcBlock(11, 12), new EcBlock(5, 13)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 15:
|
||||
$patterns = array(6, 26, 48, 70);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(22, new EcBlock(5, 87), new EcBlock(1, 88)),
|
||||
new EcBlocks(24, new EcBlock(5, 41), new EcBlock(5, 42)),
|
||||
new EcBlocks(30, new EcBlock(5, 24), new EcBlock(7, 25)),
|
||||
new EcBlocks(24, new EcBlock(11, 12), new EcBlock(7, 13)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 16:
|
||||
$patterns = array(6, 26, 50, 74);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(24, new EcBlock(5, 98), new EcBlock(1, 99)),
|
||||
new EcBlocks(28, new EcBlock(7, 45), new EcBlock(3, 46)),
|
||||
new EcBlocks(24, new EcBlock(15, 19), new EcBlock(2, 20)),
|
||||
new EcBlocks(30, new EcBlock(3, 15), new EcBlock(13, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 17:
|
||||
$patterns = array(6, 30, 54, 78);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(28, new EcBlock(1, 107), new EcBlock(5, 108)),
|
||||
new EcBlocks(28, new EcBlock(10, 46), new EcBlock(1, 47)),
|
||||
new EcBlocks(28, new EcBlock(1, 22), new EcBlock(15, 23)),
|
||||
new EcBlocks(28, new EcBlock(2, 14), new EcBlock(17, 15)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 18:
|
||||
$patterns = array(6, 30, 56, 82);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(5, 120), new EcBlock(1, 121)),
|
||||
new EcBlocks(26, new EcBlock(9, 43), new EcBlock(4, 44)),
|
||||
new EcBlocks(28, new EcBlock(17, 22), new EcBlock(1, 23)),
|
||||
new EcBlocks(28, new EcBlock(2, 14), new EcBlock(19, 15)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 19:
|
||||
$patterns = array(6, 30, 58, 86);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(28, new EcBlock(3, 113), new EcBlock(4, 114)),
|
||||
new EcBlocks(26, new EcBlock(3, 44), new EcBlock(11, 45)),
|
||||
new EcBlocks(26, new EcBlock(17, 21), new EcBlock(4, 22)),
|
||||
new EcBlocks(26, new EcBlock(9, 13), new EcBlock(16, 14)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 20:
|
||||
$patterns = array(6, 34, 62, 90);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(28, new EcBlock(3, 107), new EcBlock(5, 108)),
|
||||
new EcBlocks(26, new EcBlock(3, 41), new EcBlock(13, 42)),
|
||||
new EcBlocks(30, new EcBlock(15, 24), new EcBlock(5, 25)),
|
||||
new EcBlocks(28, new EcBlock(15, 15), new EcBlock(10, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 21:
|
||||
$patterns = array(6, 28, 50, 72, 94);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(28, new EcBlock(4, 116), new EcBlock(4, 117)),
|
||||
new EcBlocks(26, new EcBlock(17, 42)),
|
||||
new EcBlocks(28, new EcBlock(17, 22), new EcBlock(6, 23)),
|
||||
new EcBlocks(30, new EcBlock(19, 16), new EcBlock(6, 17)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 22:
|
||||
$patterns = array(6, 26, 50, 74, 98);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(28, new EcBlock(2, 111), new EcBlock(7, 112)),
|
||||
new EcBlocks(28, new EcBlock(17, 46)),
|
||||
new EcBlocks(30, new EcBlock(7, 24), new EcBlock(16, 25)),
|
||||
new EcBlocks(24, new EcBlock(34, 13)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 23:
|
||||
$patterns = array(6, 30, 54, 78, 102);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(4, 121), new EcBlock(5, 122)),
|
||||
new EcBlocks(28, new EcBlock(4, 47), new EcBlock(14, 48)),
|
||||
new EcBlocks(30, new EcBlock(11, 24), new EcBlock(14, 25)),
|
||||
new EcBlocks(30, new EcBlock(16, 15), new EcBlock(14, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 24:
|
||||
$patterns = array(6, 28, 54, 80, 106);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(6, 117), new EcBlock(4, 118)),
|
||||
new EcBlocks(28, new EcBlock(6, 45), new EcBlock(14, 46)),
|
||||
new EcBlocks(30, new EcBlock(11, 24), new EcBlock(16, 25)),
|
||||
new EcBlocks(30, new EcBlock(30, 16), new EcBlock(2, 17)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 25:
|
||||
$patterns = array(6, 32, 58, 84, 110);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(26, new EcBlock(8, 106), new EcBlock(4, 107)),
|
||||
new EcBlocks(28, new EcBlock(8, 47), new EcBlock(13, 48)),
|
||||
new EcBlocks(30, new EcBlock(7, 24), new EcBlock(22, 25)),
|
||||
new EcBlocks(30, new EcBlock(22, 15), new EcBlock(13, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 26:
|
||||
$patterns = array(6, 30, 58, 86, 114);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(28, new EcBlock(10, 114), new EcBlock(2, 115)),
|
||||
new EcBlocks(28, new EcBlock(19, 46), new EcBlock(4, 47)),
|
||||
new EcBlocks(28, new EcBlock(28, 22), new EcBlock(6, 23)),
|
||||
new EcBlocks(30, new EcBlock(33, 16), new EcBlock(4, 17)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 27:
|
||||
$patterns = array(6, 34, 62, 90, 118);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(8, 122), new EcBlock(4, 123)),
|
||||
new EcBlocks(28, new EcBlock(22, 45), new EcBlock(3, 46)),
|
||||
new EcBlocks(30, new EcBlock(8, 23), new EcBlock(26, 24)),
|
||||
new EcBlocks(30, new EcBlock(12, 15), new EcBlock(28, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 28:
|
||||
$patterns = array(6, 26, 50, 74, 98, 122);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(3, 117), new EcBlock(10, 118)),
|
||||
new EcBlocks(28, new EcBlock(3, 45), new EcBlock(23, 46)),
|
||||
new EcBlocks(30, new EcBlock(4, 24), new EcBlock(31, 25)),
|
||||
new EcBlocks(30, new EcBlock(11, 15), new EcBlock(31, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 29:
|
||||
$patterns = array(6, 30, 54, 78, 102, 126);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(7, 116), new EcBlock(7, 117)),
|
||||
new EcBlocks(28, new EcBlock(21, 45), new EcBlock(7, 46)),
|
||||
new EcBlocks(30, new EcBlock(1, 23), new EcBlock(37, 24)),
|
||||
new EcBlocks(30, new EcBlock(19, 15), new EcBlock(26, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 30:
|
||||
$patterns = array(6, 26, 52, 78, 104, 130);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(5, 115), new EcBlock(10, 116)),
|
||||
new EcBlocks(28, new EcBlock(19, 47), new EcBlock(10, 48)),
|
||||
new EcBlocks(30, new EcBlock(15, 24), new EcBlock(25, 25)),
|
||||
new EcBlocks(30, new EcBlock(23, 15), new EcBlock(25, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 31:
|
||||
$patterns = array(6, 30, 56, 82, 108, 134);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(13, 115), new EcBlock(3, 116)),
|
||||
new EcBlocks(28, new EcBlock(2, 46), new EcBlock(29, 47)),
|
||||
new EcBlocks(30, new EcBlock(42, 24), new EcBlock(1, 25)),
|
||||
new EcBlocks(30, new EcBlock(23, 15), new EcBlock(28, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 32:
|
||||
$patterns = array(6, 34, 60, 86, 112, 138);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(17, 115)),
|
||||
new EcBlocks(28, new EcBlock(10, 46), new EcBlock(23, 47)),
|
||||
new EcBlocks(30, new EcBlock(10, 24), new EcBlock(35, 25)),
|
||||
new EcBlocks(30, new EcBlock(19, 15), new EcBlock(35, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 33:
|
||||
$patterns = array(6, 30, 58, 86, 114, 142);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(17, 115), new EcBlock(1, 116)),
|
||||
new EcBlocks(28, new EcBlock(14, 46), new EcBlock(21, 47)),
|
||||
new EcBlocks(30, new EcBlock(29, 24), new EcBlock(19, 25)),
|
||||
new EcBlocks(30, new EcBlock(11, 15), new EcBlock(46, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 34:
|
||||
$patterns = array(6, 34, 62, 90, 118, 146);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(13, 115), new EcBlock(6, 116)),
|
||||
new EcBlocks(28, new EcBlock(14, 46), new EcBlock(23, 47)),
|
||||
new EcBlocks(30, new EcBlock(44, 24), new EcBlock(7, 25)),
|
||||
new EcBlocks(30, new EcBlock(59, 16), new EcBlock(1, 17)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 35:
|
||||
$patterns = array(6, 30, 54, 78, 102, 126, 150);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(12, 121), new EcBlock(7, 122)),
|
||||
new EcBlocks(28, new EcBlock(12, 47), new EcBlock(26, 48)),
|
||||
new EcBlocks(30, new EcBlock(39, 24), new EcBlock(14, 25)),
|
||||
new EcBlocks(30, new EcBlock(22, 15), new EcBlock(41, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 36:
|
||||
$patterns = array(6, 24, 50, 76, 102, 128, 154);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(6, 121), new EcBlock(14, 122)),
|
||||
new EcBlocks(28, new EcBlock(6, 47), new EcBlock(34, 48)),
|
||||
new EcBlocks(30, new EcBlock(46, 24), new EcBlock(10, 25)),
|
||||
new EcBlocks(30, new EcBlock(2, 15), new EcBlock(64, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 37:
|
||||
$patterns = array(6, 28, 54, 80, 106, 132, 158);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(17, 122), new EcBlock(4, 123)),
|
||||
new EcBlocks(28, new EcBlock(29, 46), new EcBlock(14, 47)),
|
||||
new EcBlocks(30, new EcBlock(49, 24), new EcBlock(10, 25)),
|
||||
new EcBlocks(30, new EcBlock(24, 15), new EcBlock(46, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 38:
|
||||
$patterns = array(6, 32, 58, 84, 110, 136, 162);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(4, 122), new EcBlock(18, 123)),
|
||||
new EcBlocks(28, new EcBlock(13, 46), new EcBlock(32, 47)),
|
||||
new EcBlocks(30, new EcBlock(48, 24), new EcBlock(14, 25)),
|
||||
new EcBlocks(30, new EcBlock(42, 15), new EcBlock(32, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 39:
|
||||
$patterns = array(6, 26, 54, 82, 110, 138, 166);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(20, 117), new EcBlock(4, 118)),
|
||||
new EcBlocks(28, new EcBlock(40, 47), new EcBlock(7, 48)),
|
||||
new EcBlocks(30, new EcBlock(43, 24), new EcBlock(22, 25)),
|
||||
new EcBlocks(30, new EcBlock(10, 15), new EcBlock(67, 16)),
|
||||
);
|
||||
break;
|
||||
|
||||
case 40:
|
||||
$patterns = array(6, 30, 58, 86, 114, 142, 170);
|
||||
$ecBlocks = array(
|
||||
new EcBlocks(30, new EcBlock(19, 118), new EcBlock(6, 119)),
|
||||
new EcBlocks(28, new EcBlock(18, 47), new EcBlock(31, 48)),
|
||||
new EcBlocks(30, new EcBlock(34, 24), new EcBlock(34, 25)),
|
||||
new EcBlocks(30, new EcBlock(20, 15), new EcBlock(61, 16)),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
self::$versions[$versionNumber - 1] = new self(
|
||||
$versionNumber,
|
||||
SplFixedArray::fromArray($patterns, false),
|
||||
SplFixedArray::fromArray($ecBlocks, false)
|
||||
);
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Block pair.
|
||||
*/
|
||||
class BlockPair
|
||||
{
|
||||
/**
|
||||
* Data bytes in the block.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
protected $dataBytes;
|
||||
|
||||
/**
|
||||
* Error correction bytes in the block.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
protected $errorCorrectionBytes;
|
||||
|
||||
/**
|
||||
* Creates a new block pair.
|
||||
*
|
||||
* @param SplFixedArray $data
|
||||
* @param SplFixedArray $errorCorrection
|
||||
*/
|
||||
public function __construct(SplFixedArray $data, SplFixedArray $errorCorrection)
|
||||
{
|
||||
$this->dataBytes = $data;
|
||||
$this->errorCorrectionBytes = $errorCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data bytes.
|
||||
*
|
||||
* @return SplFixedArray
|
||||
*/
|
||||
public function getDataBytes()
|
||||
{
|
||||
return $this->dataBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error correction bytes.
|
||||
*
|
||||
* @return SplFixedArray
|
||||
*/
|
||||
public function getErrorCorrectionBytes()
|
||||
{
|
||||
return $this->errorCorrectionBytes;
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Byte matrix.
|
||||
*/
|
||||
class ByteMatrix
|
||||
{
|
||||
/**
|
||||
* Bytes in the matrix, represented as array.
|
||||
*
|
||||
* @var SplFixedArray
|
||||
*/
|
||||
protected $bytes;
|
||||
|
||||
/**
|
||||
* Width of the matrix.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $width;
|
||||
|
||||
/**
|
||||
* Height of the matrix.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $height;
|
||||
|
||||
/**
|
||||
* Creates a new byte matrix.
|
||||
*
|
||||
* @param integer $width
|
||||
* @param integer $height
|
||||
*/
|
||||
public function __construct($width, $height)
|
||||
{
|
||||
$this->height = $height;
|
||||
$this->width = $width;
|
||||
$this->bytes = new SplFixedArray($height);
|
||||
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
$this->bytes[$y] = new SplFixedArray($width);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of the matrix.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height of the matrix.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the internal representation of the matrix.
|
||||
*
|
||||
* @return SplFixedArray
|
||||
*/
|
||||
public function getArray()
|
||||
{
|
||||
return $this->bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the byte for a specific position.
|
||||
*
|
||||
* @param integer $x
|
||||
* @param integer $y
|
||||
* @return integer
|
||||
*/
|
||||
public function get($x, $y)
|
||||
{
|
||||
return $this->bytes[$y][$x];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the byte for a specific position.
|
||||
*
|
||||
* @param integer $x
|
||||
* @param integer $y
|
||||
* @param integer $value
|
||||
* @return void
|
||||
*/
|
||||
public function set($x, $y, $value)
|
||||
{
|
||||
$this->bytes[$y][$x] = (int) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the matrix with a specific value.
|
||||
*
|
||||
* @param integer $value
|
||||
* @return void
|
||||
*/
|
||||
public function clear($value)
|
||||
{
|
||||
for ($y = 0; $y < $this->height; $y++) {
|
||||
for ($x = 0; $x < $this->width; $x++) {
|
||||
$this->bytes[$y][$x] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the matrix.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$result = '';
|
||||
|
||||
for ($y = 0; $y < $this->height; $y++) {
|
||||
for ($x = 0; $x < $this->width; $x++) {
|
||||
switch ($this->bytes[$y][$x]) {
|
||||
case 0:
|
||||
$result .= ' 0';
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$result .= ' 1';
|
||||
break;
|
||||
|
||||
default:
|
||||
$result .= ' ';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$result .= "\n";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+687
@@ -0,0 +1,687 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use BaconQrCode\Common\BitArray;
|
||||
use BaconQrCode\Common\CharacterSetEci;
|
||||
use BaconQrCode\Common\ErrorCorrectionLevel;
|
||||
use BaconQrCode\Common\Mode;
|
||||
use BaconQrCode\Common\ReedSolomonCodec;
|
||||
use BaconQrCode\Common\Version;
|
||||
use BaconQrCode\Exception;
|
||||
use SplFixedArray;
|
||||
|
||||
/**
|
||||
* Encoder.
|
||||
*/
|
||||
class Encoder
|
||||
{
|
||||
/**
|
||||
* Default byte encoding.
|
||||
*/
|
||||
const DEFAULT_BYTE_MODE_ECODING = 'ISO-8859-1';
|
||||
|
||||
/**
|
||||
* The original table is defined in the table 5 of JISX0510:2004 (p.19).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $alphanumericTable = array(
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f
|
||||
36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f
|
||||
-1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f
|
||||
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f
|
||||
);
|
||||
|
||||
/**
|
||||
* Codec cache.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $codecs = array();
|
||||
|
||||
/**
|
||||
* Encodes "content" with the error correction level "ecLevel".
|
||||
*
|
||||
* @param string $content
|
||||
* @param ErrorCorrectionLevel $ecLevel
|
||||
* @param ? $hints
|
||||
* @return QrCode
|
||||
*/
|
||||
public static function encode($content, ErrorCorrectionLevel $ecLevel, $encoding = self::DEFAULT_BYTE_MODE_ECODING)
|
||||
{
|
||||
// Pick an encoding mode appropriate for the content. Note that this
|
||||
// will not attempt to use multiple modes / segments even if that were
|
||||
// more efficient. Would be nice.
|
||||
$mode = self::chooseMode($content, $encoding);
|
||||
|
||||
// This will store the header information, like mode and length, as well
|
||||
// as "header" segments like an ECI segment.
|
||||
$headerBits = new BitArray();
|
||||
|
||||
// Append ECI segment if applicable
|
||||
if ($mode->get() === Mode::BYTE && $encoding !== self::DEFAULT_BYTE_MODE_ECODING) {
|
||||
$eci = CharacterSetEci::getCharacterSetEciByName($encoding);
|
||||
|
||||
if ($eci !== null) {
|
||||
self::appendEci($eci, $headerBits);
|
||||
}
|
||||
}
|
||||
|
||||
// (With ECI in place,) Write the mode marker
|
||||
self::appendModeInfo($mode, $headerBits);
|
||||
|
||||
// Collect data within the main segment, separately, to count its size
|
||||
// if needed. Don't add it to main payload yet.
|
||||
$dataBits = new BitArray();
|
||||
self::appendBytes($content, $mode, $dataBits, $encoding);
|
||||
|
||||
// Hard part: need to know version to know how many bits length takes.
|
||||
// But need to know how many bits it takes to know version. First we
|
||||
// take a guess at version by assuming version will be the minimum, 1:
|
||||
$provisionalBitsNeeded = $headerBits->getSize()
|
||||
+ $mode->getCharacterCountBits(Version::getVersionForNumber(1))
|
||||
+ $dataBits->getSize();
|
||||
$provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel);
|
||||
|
||||
// Use that guess to calculate the right version. I am still not sure
|
||||
// this works in 100% of cases.
|
||||
$bitsNeeded = $headerBits->getSize()
|
||||
+ $mode->getCharacterCountBits($provisionalVersion)
|
||||
+ $dataBits->getSize();
|
||||
$version = self::chooseVersion($bitsNeeded, $ecLevel);
|
||||
|
||||
$headerAndDataBits = new BitArray();
|
||||
$headerAndDataBits->appendBitArray($headerBits);
|
||||
|
||||
// Find "length" of main segment and write it.
|
||||
$numLetters = ($mode->get() === Mode::BYTE ? $dataBits->getSizeInBytes() : strlen($content));
|
||||
self::appendLengthInfo($numLetters, $version, $mode, $headerAndDataBits);
|
||||
|
||||
// Put data together into the overall payload.
|
||||
$headerAndDataBits->appendBitArray($dataBits);
|
||||
$ecBlocks = $version->getEcBlocksForLevel($ecLevel);
|
||||
$numDataBytes = $version->getTotalCodewords() - $ecBlocks->getTotalEcCodewords();
|
||||
|
||||
// Terminate the bits properly.
|
||||
self::terminateBits($numDataBytes, $headerAndDataBits);
|
||||
|
||||
// Interleave data bits with error correction code.
|
||||
$finalBits = self::interleaveWithEcBytes(
|
||||
$headerAndDataBits,
|
||||
$version->getTotalCodewords(),
|
||||
$numDataBytes,
|
||||
$ecBlocks->getNumBlocks()
|
||||
);
|
||||
|
||||
$qrCode = new QrCode();
|
||||
$qrCode->setErrorCorrectionLevel($ecLevel);
|
||||
$qrCode->setMode($mode);
|
||||
$qrCode->setVersion($version);
|
||||
|
||||
// Choose the mask pattern and set to "qrCode".
|
||||
$dimension = $version->getDimensionForVersion();
|
||||
$matrix = new ByteMatrix($dimension, $dimension);
|
||||
$maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix);
|
||||
$qrCode->setMaskPattern($maskPattern);
|
||||
|
||||
// Build the matrix and set it to "qrCode".
|
||||
MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix);
|
||||
$qrCode->setMatrix($matrix);
|
||||
|
||||
return $qrCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the alphanumeric code for a byte.
|
||||
*
|
||||
* @param string|integer $code
|
||||
* @return integer
|
||||
*/
|
||||
protected static function getAlphanumericCode($code)
|
||||
{
|
||||
$code = (is_string($code) ? ord($code) : $code);
|
||||
|
||||
if (isset(self::$alphanumericTable[$code])) {
|
||||
return self::$alphanumericTable[$code];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses the best mode for a given content.
|
||||
*
|
||||
* @param string $content
|
||||
* @param string $encoding
|
||||
* @return Mode
|
||||
*/
|
||||
protected static function chooseMode($content, $encoding = null)
|
||||
{
|
||||
if (strcasecmp($encoding, 'SHIFT-JIS') === 0) {
|
||||
return self::isOnlyDoubleByteKanji($content) ? new Mode(Mode::KANJI) : new Mode(Mode::BYTE);
|
||||
}
|
||||
|
||||
$hasNumeric = false;
|
||||
$hasAlphanumeric = false;
|
||||
$contentLength = strlen($content);
|
||||
|
||||
for ($i = 0; $i < $contentLength; $i++) {
|
||||
$char = $content[$i];
|
||||
|
||||
if (ctype_digit($char)) {
|
||||
$hasNumeric = true;
|
||||
} elseif (self::getAlphanumericCode($char) !== -1) {
|
||||
$hasAlphanumeric = true;
|
||||
} else {
|
||||
return new Mode(Mode::BYTE);
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasAlphanumeric) {
|
||||
return new Mode(Mode::ALPHANUMERIC);
|
||||
} elseif ($hasNumeric) {
|
||||
return new Mode(Mode::NUMERIC);
|
||||
}
|
||||
|
||||
return new Mode(Mode::BYTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the mask penalty for a matrix.
|
||||
*
|
||||
* @param ByteMatrix $matrix
|
||||
* @return integer
|
||||
*/
|
||||
protected static function calculateMaskPenalty(ByteMatrix $matrix)
|
||||
{
|
||||
return (
|
||||
MaskUtil::applyMaskPenaltyRule1($matrix)
|
||||
+ MaskUtil::applyMaskPenaltyRule2($matrix)
|
||||
+ MaskUtil::applyMaskPenaltyRule3($matrix)
|
||||
+ MaskUtil::applyMaskPenaltyRule4($matrix)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses the best mask pattern for a matrix.
|
||||
*
|
||||
* @param BitArray $bits
|
||||
* @param ErrorCorrectionLevel $ecLevel
|
||||
* @param Version $version
|
||||
* @param ByteMatrix $matrix
|
||||
* @return integer
|
||||
*/
|
||||
protected static function chooseMaskPattern(
|
||||
BitArray $bits,
|
||||
ErrorCorrectionLevel $ecLevel,
|
||||
Version $version,
|
||||
ByteMatrix $matrix
|
||||
) {
|
||||
$minPenality = PHP_INT_MAX;
|
||||
$bestMaskPattern = -1;
|
||||
|
||||
for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; $maskPattern++) {
|
||||
MatrixUtil::buildMatrix($bits, $ecLevel, $version, $maskPattern, $matrix);
|
||||
$penalty = self::calculateMaskPenalty($matrix);
|
||||
|
||||
if ($penalty < $minPenality) {
|
||||
$minPenality = $penalty;
|
||||
$bestMaskPattern = $maskPattern;
|
||||
}
|
||||
}
|
||||
|
||||
return $bestMaskPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses the best version for the input.
|
||||
*
|
||||
* @param integer $numInputBits
|
||||
* @param ErrorCorrectionLevel $ecLevel
|
||||
* @return Version
|
||||
* @throws Exception\WriterException
|
||||
*/
|
||||
protected static function chooseVersion($numInputBits, ErrorCorrectionLevel $ecLevel)
|
||||
{
|
||||
for ($versionNum = 1; $versionNum <= 40; $versionNum++) {
|
||||
$version = Version::getVersionForNumber($versionNum);
|
||||
$numBytes = $version->getTotalCodewords();
|
||||
|
||||
$ecBlocks = $version->getEcBlocksForLevel($ecLevel);
|
||||
$numEcBytes = $ecBlocks->getTotalEcCodewords();
|
||||
|
||||
$numDataBytes = $numBytes - $numEcBytes;
|
||||
$totalInputBytes = intval(($numInputBits + 8) / 8);
|
||||
|
||||
if ($numDataBytes >= $totalInputBytes) {
|
||||
return $version;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception\WriterException('Data too big');
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates the bits in a bit array.
|
||||
*
|
||||
* @param integer $numDataBytes
|
||||
* @param BitArray $bits
|
||||
* @throws Exception\WriterException
|
||||
*/
|
||||
protected static function terminateBits($numDataBytes, BitArray $bits)
|
||||
{
|
||||
$capacity = $numDataBytes << 3;
|
||||
|
||||
if ($bits->getSize() > $capacity) {
|
||||
throw new Exception\WriterException('Data bits cannot fit in the QR code');
|
||||
}
|
||||
|
||||
for ($i = 0; $i < 4 && $bits->getSize() < $capacity; $i++) {
|
||||
$bits->appendBit(false);
|
||||
}
|
||||
|
||||
$numBitsInLastByte = $bits->getSize() & 0x7;
|
||||
|
||||
if ($numBitsInLastByte > 0) {
|
||||
for ($i = $numBitsInLastByte; $i < 8; $i++) {
|
||||
$bits->appendBit(false);
|
||||
}
|
||||
}
|
||||
|
||||
$numPaddingBytes = $numDataBytes - $bits->getSizeInBytes();
|
||||
|
||||
for ($i = 0; $i < $numPaddingBytes; $i++) {
|
||||
$bits->appendBits(($i & 0x1) === 0 ? 0xec : 0x11, 8);
|
||||
}
|
||||
|
||||
if ($bits->getSize() !== $capacity) {
|
||||
throw new Exception\WriterException('Bits size does not equal capacity');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets number of data- and EC bytes for a block ID.
|
||||
*
|
||||
* @param integer $numTotalBytes
|
||||
* @param integer $numDataBytes
|
||||
* @param integer $numRsBlocks
|
||||
* @param integer $blockId
|
||||
* @return array
|
||||
* @throws Exception\WriterException
|
||||
*/
|
||||
protected static function getNumDataBytesAndNumEcBytesForBlockId(
|
||||
$numTotalBytes,
|
||||
$numDataBytes,
|
||||
$numRsBlocks,
|
||||
$blockId
|
||||
) {
|
||||
if ($blockId >= $numRsBlocks) {
|
||||
throw new Exception\WriterException('Block ID too large');
|
||||
}
|
||||
|
||||
$numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks;
|
||||
$numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2;
|
||||
$numTotalBytesInGroup1 = intval($numTotalBytes / $numRsBlocks);
|
||||
$numTotalBytesInGroup2 = $numTotalBytesInGroup1 + 1;
|
||||
$numDataBytesInGroup1 = intval($numDataBytes / $numRsBlocks);
|
||||
$numDataBytesInGroup2 = $numDataBytesInGroup1 + 1;
|
||||
$numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1;
|
||||
$numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2;
|
||||
|
||||
if ($numEcBytesInGroup1 !== $numEcBytesInGroup2) {
|
||||
throw new Exception\WriterException('EC bytes mismatch');
|
||||
}
|
||||
|
||||
if ($numRsBlocks !== $numRsBlocksInGroup1 + $numRsBlocksInGroup2) {
|
||||
throw new Exception\WriterException('RS blocks mismatch');
|
||||
}
|
||||
|
||||
if ($numTotalBytes !==
|
||||
(($numDataBytesInGroup1 + $numEcBytesInGroup1) * $numRsBlocksInGroup1)
|
||||
+ (($numDataBytesInGroup2 + $numEcBytesInGroup2) * $numRsBlocksInGroup2)
|
||||
) {
|
||||
throw new Exception\WriterException('Total bytes mismatch');
|
||||
}
|
||||
|
||||
if ($blockId < $numRsBlocksInGroup1) {
|
||||
return array($numDataBytesInGroup1, $numEcBytesInGroup1);
|
||||
} else {
|
||||
return array($numDataBytesInGroup2, $numEcBytesInGroup2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interleaves data with EC bytes.
|
||||
*
|
||||
* @param BitArray $bits
|
||||
* @param integer $numTotalBytes
|
||||
* @param integer $numDataBytes
|
||||
* @param integer $numRsBlocks
|
||||
* @return BitArray
|
||||
* @throws Exception\WriterException
|
||||
*/
|
||||
protected static function interleaveWithEcBytes(BitArray $bits, $numTotalBytes, $numDataBytes, $numRsBlocks)
|
||||
{
|
||||
if ($bits->getSizeInBytes() !== $numDataBytes) {
|
||||
throw new Exception\WriterException('Number of bits and data bytes does not match');
|
||||
}
|
||||
|
||||
$dataBytesOffset = 0;
|
||||
$maxNumDataBytes = 0;
|
||||
$maxNumEcBytes = 0;
|
||||
|
||||
$blocks = new SplFixedArray($numRsBlocks);
|
||||
|
||||
for ($i = 0; $i < $numRsBlocks; $i++) {
|
||||
list($numDataBytesInBlock, $numEcBytesInBlock) = self::getNumDataBytesAndNumEcBytesForBlockId(
|
||||
$numTotalBytes,
|
||||
$numDataBytes,
|
||||
$numRsBlocks,
|
||||
$i
|
||||
);
|
||||
|
||||
$size = $numDataBytesInBlock;
|
||||
$dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size);
|
||||
$ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock);
|
||||
$blocks[$i] = new BlockPair($dataBytes, $ecBytes);
|
||||
|
||||
$maxNumDataBytes = max($maxNumDataBytes, $size);
|
||||
$maxNumEcBytes = max($maxNumEcBytes, count($ecBytes));
|
||||
$dataBytesOffset += $numDataBytesInBlock;
|
||||
}
|
||||
|
||||
if ($numDataBytes !== $dataBytesOffset) {
|
||||
throw new Exception\WriterException('Data bytes does not match offset');
|
||||
}
|
||||
|
||||
$result = new BitArray();
|
||||
|
||||
for ($i = 0; $i < $maxNumDataBytes; $i++) {
|
||||
foreach ($blocks as $block) {
|
||||
$dataBytes = $block->getDataBytes();
|
||||
|
||||
if ($i < count($dataBytes)) {
|
||||
$result->appendBits($dataBytes[$i], 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $maxNumEcBytes; $i++) {
|
||||
foreach ($blocks as $block) {
|
||||
$ecBytes = $block->getErrorCorrectionBytes();
|
||||
|
||||
if ($i < count($ecBytes)) {
|
||||
$result->appendBits($ecBytes[$i], 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($numTotalBytes !== $result->getSizeInBytes()) {
|
||||
throw new Exception\WriterException('Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates EC bytes for given data.
|
||||
*
|
||||
* @param SplFixedArray $dataBytes
|
||||
* @param integer $numEcBytesInBlock
|
||||
* @return SplFixedArray
|
||||
*/
|
||||
protected static function generateEcBytes(SplFixedArray $dataBytes, $numEcBytesInBlock)
|
||||
{
|
||||
$numDataBytes = count($dataBytes);
|
||||
$toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock);
|
||||
|
||||
for ($i = 0; $i < $numDataBytes; $i++) {
|
||||
$toEncode[$i] = $dataBytes[$i] & 0xff;
|
||||
}
|
||||
|
||||
$ecBytes = new SplFixedArray($numEcBytesInBlock);
|
||||
$codec = self::getCodec($numDataBytes, $numEcBytesInBlock);
|
||||
$codec->encode($toEncode, $ecBytes);
|
||||
|
||||
return $ecBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an RS codec and caches it.
|
||||
*
|
||||
* @param integer $numDataBytes
|
||||
* @param integer $numEcBytesInBlock
|
||||
* @return ReedSolomonCodec
|
||||
*/
|
||||
protected static function getCodec($numDataBytes, $numEcBytesInBlock)
|
||||
{
|
||||
$cacheId = $numDataBytes . '-' . $numEcBytesInBlock;
|
||||
|
||||
if (!isset(self::$codecs[$cacheId])) {
|
||||
self::$codecs[$cacheId] = new ReedSolomonCodec(
|
||||
8,
|
||||
0x11d,
|
||||
0,
|
||||
1,
|
||||
$numEcBytesInBlock,
|
||||
255 - $numDataBytes - $numEcBytesInBlock
|
||||
);
|
||||
}
|
||||
|
||||
return self::$codecs[$cacheId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends mode information to a bit array.
|
||||
*
|
||||
* @param Mode $mode
|
||||
* @param BitArray $bits
|
||||
* @return void
|
||||
*/
|
||||
protected static function appendModeInfo(Mode $mode, BitArray $bits)
|
||||
{
|
||||
$bits->appendBits($mode->get(), 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends length information to a bit array.
|
||||
*
|
||||
* @param integer $numLetters
|
||||
* @param Version $version
|
||||
* @param Mode $mode
|
||||
* @param BitArray $bits
|
||||
* @return void
|
||||
* @throws Exception\WriterException
|
||||
*/
|
||||
protected static function appendLengthInfo($numLetters, Version $version, Mode $mode, BitArray $bits)
|
||||
{
|
||||
$numBits = $mode->getCharacterCountBits($version);
|
||||
|
||||
if ($numLetters >= (1 << $numBits)) {
|
||||
throw new Exception\WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1));
|
||||
}
|
||||
|
||||
$bits->appendBits($numLetters, $numBits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends bytes to a bit array in a specific mode.
|
||||
*
|
||||
* @param stirng $content
|
||||
* @param Mode $mode
|
||||
* @param BitArray $bits
|
||||
* @param string $encoding
|
||||
* @return void
|
||||
* @throws Exception\WriterException
|
||||
*/
|
||||
protected static function appendBytes($content, Mode $mode, BitArray $bits, $encoding)
|
||||
{
|
||||
switch ($mode->get()) {
|
||||
case Mode::NUMERIC:
|
||||
self::appendNumericBytes($content, $bits);
|
||||
break;
|
||||
|
||||
case Mode::ALPHANUMERIC:
|
||||
self::appendAlphanumericBytes($content, $bits);
|
||||
break;
|
||||
|
||||
case Mode::BYTE:
|
||||
self::append8BitBytes($content, $bits, $encoding);
|
||||
break;
|
||||
|
||||
case Mode::KANJI:
|
||||
self::appendKanjiBytes($content, $bits);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception\WriterException('Invalid mode: ' . $mode->get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends numeric bytes to a bit array.
|
||||
*
|
||||
* @param string $content
|
||||
* @param BitArray $bits
|
||||
* @return void
|
||||
*/
|
||||
protected static function appendNumericBytes($content, BitArray $bits)
|
||||
{
|
||||
$length = strlen($content);
|
||||
$i = 0;
|
||||
|
||||
while ($i < $length) {
|
||||
$num1 = (int) $content[$i];
|
||||
|
||||
if ($i + 2 < $length) {
|
||||
// Encode three numeric letters in ten bits.
|
||||
$num2 = (int) $content[$i + 1];
|
||||
$num3 = (int) $content[$i + 2];
|
||||
$bits->appendBits($num1 * 100 + $num2 * 10 + $num3, 10);
|
||||
$i += 3;
|
||||
} elseif ($i + 1 < $length) {
|
||||
// Encode two numeric letters in seven bits.
|
||||
$num2 = (int) $content[$i + 1];
|
||||
$bits->appendBits($num1 * 10 + $num2, 7);
|
||||
$i += 2;
|
||||
} else {
|
||||
// Encode one numeric letter in four bits.
|
||||
$bits->appendBits($num1, 4);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends alpha-numeric bytes to a bit array.
|
||||
*
|
||||
* @param string $content
|
||||
* @param BitArray $bits
|
||||
* @return void
|
||||
*/
|
||||
protected static function appendAlphanumericBytes($content, BitArray $bits)
|
||||
{
|
||||
$length = strlen($content);
|
||||
$i = 0;
|
||||
|
||||
while ($i < $length) {
|
||||
if (-1 === ($code1 = self::getAlphanumericCode($content[$i]))) {
|
||||
throw new Exception\WriterException('Invalid alphanumeric code');
|
||||
}
|
||||
|
||||
if ($i + 1 < $length) {
|
||||
if (-1 === ($code2 = self::getAlphanumericCode($content[$i + 1]))) {
|
||||
throw new Exception\WriterException('Invalid alphanumeric code');
|
||||
}
|
||||
|
||||
// Encode two alphanumeric letters in 11 bits.
|
||||
$bits->appendBits($code1 * 45 + $code2, 11);
|
||||
$i += 2;
|
||||
} else {
|
||||
// Encode one alphanumeric letter in six bits.
|
||||
$bits->appendBits($code1, 6);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends regular 8-bit bytes to a bit array.
|
||||
*
|
||||
* @param string $content
|
||||
* @param BitArray $bits
|
||||
* @return void
|
||||
*/
|
||||
protected static function append8BitBytes($content, BitArray $bits, $encoding)
|
||||
{
|
||||
if (false === ($bytes = @iconv('utf-8', $encoding, $content))) {
|
||||
throw new Exception\WriterException('Could not encode content to ' . $encoding);
|
||||
}
|
||||
|
||||
$length = strlen($bytes);
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$bits->appendBits(ord($bytes[$i]), 8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends KANJI bytes to a bit array.
|
||||
*
|
||||
* @param string $content
|
||||
* @param BitArray $bits
|
||||
* @return void
|
||||
*/
|
||||
protected static function appendKanjiBytes($content, BitArray $bits)
|
||||
{
|
||||
if (strlen($content) % 2 > 0) {
|
||||
// We just do a simple length check here. The for loop will check
|
||||
// individual characters.
|
||||
throw new Exception\WriterException('Content does not seem to be encoded in SHIFT-JIS');
|
||||
}
|
||||
|
||||
$length = strlen($content);
|
||||
|
||||
for ($i = 0; $i < $length; $i += 2) {
|
||||
$byte1 = ord($content[$i]) & 0xff;
|
||||
$byte2 = ord($content[$i + 1]) & 0xff;
|
||||
$code = ($byte1 << 8) | $byte2;
|
||||
|
||||
if ($code >= 0x8140 && $code <= 0x9ffc) {
|
||||
$subtracted = $code - 0x8140;
|
||||
} elseif ($code >= 0xe040 && $code <= 0xebbf) {
|
||||
$subtracted = $code - 0xc140;
|
||||
} else {
|
||||
throw new Exception\WriterException('Invalid byte sequence');
|
||||
}
|
||||
|
||||
$encoded = (($subtracted >> 8) * 0xc0) + ($subtracted & 0xff);
|
||||
|
||||
$bits->appendBits($encoded, 13);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends ECI information to a bit array.
|
||||
*
|
||||
* @param CharacterSetEci $eci
|
||||
* @param BitArray $bits
|
||||
* @return void
|
||||
*/
|
||||
protected static function appendEci(CharacterSetEci $eci, BitArray $bits)
|
||||
{
|
||||
$mode = new Mode(Mode::ECI);
|
||||
$bits->appendBits($mode->get(), 4);
|
||||
$bits->appendBits($eci->get(), 8);
|
||||
}
|
||||
}
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use BaconQrCode\Common\BitUtils;
|
||||
|
||||
/**
|
||||
* Mask utility.
|
||||
*/
|
||||
class MaskUtil
|
||||
{
|
||||
/**#@+
|
||||
* Penalty weights from section 6.8.2.1
|
||||
*/
|
||||
const N1 = 3;
|
||||
const N2 = 3;
|
||||
const N3 = 40;
|
||||
const N4 = 10;
|
||||
/**#@-*/
|
||||
|
||||
/**
|
||||
* Applies mask penalty rule 1 and returns the penalty.
|
||||
*
|
||||
* Finds repetitive cells with the same color and gives penalty to them.
|
||||
* Example: 00000 or 11111.
|
||||
*
|
||||
* @param ByteMatrix $matrix
|
||||
* @return integer
|
||||
*/
|
||||
public static function applyMaskPenaltyRule1(ByteMatrix $matrix)
|
||||
{
|
||||
return (
|
||||
self::applyMaskPenaltyRule1Internal($matrix, true)
|
||||
+ self::applyMaskPenaltyRule1Internal($matrix, false)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies mask penalty rule 2 and returns the penalty.
|
||||
*
|
||||
* Finds 2x2 blocks with the same color and gives penalty to them. This is
|
||||
* actually equivalent to the spec's rule, which is to find MxN blocks and
|
||||
* give a penalty proportional to (M-1)x(N-1), because this is the number of
|
||||
* 2x2 blocks inside such a block.
|
||||
*
|
||||
* @param ByteMatrix $matrix
|
||||
* @return integer
|
||||
*/
|
||||
public static function applyMaskPenaltyRule2(ByteMatrix $matrix)
|
||||
{
|
||||
$penalty = 0;
|
||||
$array = $matrix->getArray();
|
||||
$width = $matrix->getWidth();
|
||||
$height = $matrix->getHeight();
|
||||
|
||||
for ($y = 0; $y < $height - 1; $y++) {
|
||||
for ($x = 0; $x < $width - 1; $x++) {
|
||||
$value = $array[$y][$x];
|
||||
|
||||
if ($value === $array[$y][$x + 1] && $value === $array[$y + 1][$x] && $value === $array[$y + 1][$x + 1]) {
|
||||
$penalty++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::N2 * $penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies mask penalty rule 3 and returns the penalty.
|
||||
*
|
||||
* Finds consecutive cells of 00001011101 or 10111010000, and gives penalty
|
||||
* to them. If we find patterns like 000010111010000, we give penalties
|
||||
* twice (i.e. 40 * 2).
|
||||
*
|
||||
* @param ByteMatrix $matrix
|
||||
* @return integer
|
||||
*/
|
||||
public static function applyMaskPenaltyRule3(ByteMatrix $matrix)
|
||||
{
|
||||
$penalty = 0;
|
||||
$array = $matrix->getArray();
|
||||
$width = $matrix->getWidth();
|
||||
$height = $matrix->getHeight();
|
||||
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
for ($x = 0; $x < $width; $x++) {
|
||||
if (
|
||||
$x + 6 < $width
|
||||
&& $array[$y][$x] === 1
|
||||
&& $array[$y][$x + 1] === 0
|
||||
&& $array[$y][$x + 2] === 1
|
||||
&& $array[$y][$x + 3] === 1
|
||||
&& $array[$y][$x + 4] === 1
|
||||
&& $array[$y][$x + 5] === 0
|
||||
&& $array[$y][$x + 6] === 1
|
||||
&& (
|
||||
(
|
||||
$x + 10 < $width
|
||||
&& $array[$y][$x + 7] === 0
|
||||
&& $array[$y][$x + 8] === 0
|
||||
&& $array[$y][$x + 9] === 0
|
||||
&& $array[$y][$x + 10] === 0
|
||||
)
|
||||
|| (
|
||||
$x - 4 >= 0
|
||||
&& $array[$y][$x - 1] === 0
|
||||
&& $array[$y][$x - 2] === 0
|
||||
&& $array[$y][$x - 3] === 0
|
||||
&& $array[$y][$x - 4] === 0
|
||||
)
|
||||
)
|
||||
) {
|
||||
$penalty += self::N3;
|
||||
}
|
||||
|
||||
if (
|
||||
$y + 6 < $height
|
||||
&& $array[$y][$x] === 1
|
||||
&& $array[$y + 1][$x] === 0
|
||||
&& $array[$y + 2][$x] === 1
|
||||
&& $array[$y + 3][$x] === 1
|
||||
&& $array[$y + 4][$x] === 1
|
||||
&& $array[$y + 5][$x] === 0
|
||||
&& $array[$y + 6][$x] === 1
|
||||
&& (
|
||||
(
|
||||
$y + 10 < $height
|
||||
&& $array[$y + 7][$x] === 0
|
||||
&& $array[$y + 8][$x] === 0
|
||||
&& $array[$y + 9][$x] === 0
|
||||
&& $array[$y + 10][$x] === 0
|
||||
)
|
||||
|| (
|
||||
$y - 4 >= 0
|
||||
&& $array[$y - 1][$x] === 0
|
||||
&& $array[$y - 2][$x] === 0
|
||||
&& $array[$y - 3][$x] === 0
|
||||
&& $array[$y - 4][$x] === 0
|
||||
)
|
||||
)
|
||||
) {
|
||||
$penalty += self::N3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies mask penalty rule 4 and returns the penalty.
|
||||
*
|
||||
* Calculates the ratio of dark cells and gives penalty if the ratio is far
|
||||
* from 50%. It gives 10 penalty for 5% distance.
|
||||
*
|
||||
* @param ByteMatrix $matrix
|
||||
* @return integer
|
||||
*/
|
||||
public static function applyMaskPenaltyRule4(ByteMatrix $matrix)
|
||||
{
|
||||
$numDarkCells = 0;
|
||||
|
||||
$array = $matrix->getArray();
|
||||
$width = $matrix->getWidth();
|
||||
$height = $matrix->getHeight();
|
||||
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
$arrayY = $array[$y];
|
||||
|
||||
for ($x = 0; $x < $width; $x++) {
|
||||
if ($arrayY[$x] === 1) {
|
||||
$numDarkCells++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$numTotalCells = $height * $width;
|
||||
$darkRatio = $numDarkCells / $numTotalCells;
|
||||
$fixedPercentVariances = (int) (abs($darkRatio - 0.5) * 20);
|
||||
|
||||
return $fixedPercentVariances * self::N4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mask bit for "getMaskPattern" at "x" and "y".
|
||||
*
|
||||
* See 8.8 of JISX0510:2004 for mask pattern conditions.
|
||||
*
|
||||
* @param integer $maskPattern
|
||||
* @param integer $x
|
||||
* @param integer $y
|
||||
* @return integer
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function getDataMaskBit($maskPattern, $x, $y)
|
||||
{
|
||||
switch ($maskPattern) {
|
||||
case 0:
|
||||
$intermediate = ($y + $x) & 0x1;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$intermediate = $y & 0x1;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$intermediate = $x % 3;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$intermediate = ($y + $x) % 3;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
$intermediate = (BitUtils::unsignedRightShift($y, 1) + ($x / 3)) & 0x1;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
$temp = $y * $x;
|
||||
$intermediate = ($temp & 0x1) + ($temp % 3);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
$temp = $y * $x;
|
||||
$intermediate = (($temp & 0x1) + ($temp % 3)) & 0x1;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
$temp = $y * $x;
|
||||
$intermediate = (($temp % 3) + (($y + $x) & 0x1)) & 0x1;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception\InvalidArgumentException('Invalid mask pattern: ' . $maskPattern);
|
||||
}
|
||||
|
||||
return $intermediate === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for applyMaskPenaltyRule1.
|
||||
*
|
||||
* We need this for doing this calculation in both vertical and horizontal
|
||||
* orders respectively.
|
||||
*
|
||||
* @param ByteMatrix $matrix
|
||||
* @param boolean $isHorizontal
|
||||
* @return integer
|
||||
*/
|
||||
protected static function applyMaskPenaltyRule1Internal(ByteMatrix $matrix, $isHorizontal)
|
||||
{
|
||||
$penalty = 0;
|
||||
$iLimit = $isHorizontal ? $matrix->getHeight() : $matrix->getWidth();
|
||||
$jLimit = $isHorizontal ? $matrix->getWidth() : $matrix->getHeight();
|
||||
$array = $matrix->getArray();
|
||||
|
||||
for ($i = 0; $i < $iLimit; $i++) {
|
||||
$numSameBitCells = 0;
|
||||
$prevBit = -1;
|
||||
|
||||
for ($j = 0; $j < $jLimit; $j++) {
|
||||
$bit = $isHorizontal ? $array[$i][$j] : $array[$j][$i];
|
||||
|
||||
if ($bit === $prevBit) {
|
||||
$numSameBitCells++;
|
||||
} else {
|
||||
if ($numSameBitCells >= 5) {
|
||||
$penalty += self::N1 + ($numSameBitCells - 5);
|
||||
}
|
||||
|
||||
$numSameBitCells = 1;
|
||||
$prevBit = $bit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($numSameBitCells >= 5) {
|
||||
$penalty += self::N1 + ($numSameBitCells - 5);
|
||||
}
|
||||
}
|
||||
|
||||
return $penalty;
|
||||
}
|
||||
}
|
||||
+580
@@ -0,0 +1,580 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use BaconQrCode\Common\BitArray;
|
||||
use BaconQrCode\Common\ErrorCorrectionLevel;
|
||||
use BaconQrCode\Common\Version;
|
||||
use BaconQrCode\Exception;
|
||||
|
||||
/**
|
||||
* Matrix utility.
|
||||
*/
|
||||
class MatrixUtil
|
||||
{
|
||||
/**
|
||||
* Position detection pattern.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $positionDetectionPattern = array(
|
||||
array(1, 1, 1, 1, 1, 1, 1),
|
||||
array(1, 0, 0, 0, 0, 0, 1),
|
||||
array(1, 0, 1, 1, 1, 0, 1),
|
||||
array(1, 0, 1, 1, 1, 0, 1),
|
||||
array(1, 0, 1, 1, 1, 0, 1),
|
||||
array(1, 0, 0, 0, 0, 0, 1),
|
||||
array(1, 1, 1, 1, 1, 1, 1),
|
||||
);
|
||||
|
||||
/**
|
||||
* Position adjustment pattern.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $positionAdjustmentPattern = array(
|
||||
array(1, 1, 1, 1, 1),
|
||||
array(1, 0, 0, 0, 1),
|
||||
array(1, 0, 1, 0, 1),
|
||||
array(1, 0, 0, 0, 1),
|
||||
array(1, 1, 1, 1, 1),
|
||||
);
|
||||
|
||||
/**
|
||||
* Coordinates for position adjustment patterns for each version.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $positionAdjustmentPatternCoordinateTable = array(
|
||||
array(null, null, null, null, null, null, null), // Version 1
|
||||
array( 6, 18, null, null, null, null, null), // Version 2
|
||||
array( 6, 22, null, null, null, null, null), // Version 3
|
||||
array( 6, 26, null, null, null, null, null), // Version 4
|
||||
array( 6, 30, null, null, null, null, null), // Version 5
|
||||
array( 6, 34, null, null, null, null, null), // Version 6
|
||||
array( 6, 22, 38, null, null, null, null), // Version 7
|
||||
array( 6, 24, 42, null, null, null, null), // Version 8
|
||||
array( 6, 26, 46, null, null, null, null), // Version 9
|
||||
array( 6, 28, 50, null, null, null, null), // Version 10
|
||||
array( 6, 30, 54, null, null, null, null), // Version 11
|
||||
array( 6, 32, 58, null, null, null, null), // Version 12
|
||||
array( 6, 34, 62, null, null, null, null), // Version 13
|
||||
array( 6, 26, 46, 66, null, null, null), // Version 14
|
||||
array( 6, 26, 48, 70, null, null, null), // Version 15
|
||||
array( 6, 26, 50, 74, null, null, null), // Version 16
|
||||
array( 6, 30, 54, 78, null, null, null), // Version 17
|
||||
array( 6, 30, 56, 82, null, null, null), // Version 18
|
||||
array( 6, 30, 58, 86, null, null, null), // Version 19
|
||||
array( 6, 34, 62, 90, null, null, null), // Version 20
|
||||
array( 6, 28, 50, 72, 94, null, null), // Version 21
|
||||
array( 6, 26, 50, 74, 98, null, null), // Version 22
|
||||
array( 6, 30, 54, 78, 102, null, null), // Version 23
|
||||
array( 6, 28, 54, 80, 106, null, null), // Version 24
|
||||
array( 6, 32, 58, 84, 110, null, null), // Version 25
|
||||
array( 6, 30, 58, 86, 114, null, null), // Version 26
|
||||
array( 6, 34, 62, 90, 118, null, null), // Version 27
|
||||
array( 6, 26, 50, 74, 98, 122, null), // Version 28
|
||||
array( 6, 30, 54, 78, 102, 126, null), // Version 29
|
||||
array( 6, 26, 52, 78, 104, 130, null), // Version 30
|
||||
array( 6, 30, 56, 82, 108, 134, null), // Version 31
|
||||
array( 6, 34, 60, 86, 112, 138, null), // Version 32
|
||||
array( 6, 30, 58, 86, 114, 142, null), // Version 33
|
||||
array( 6, 34, 62, 90, 118, 146, null), // Version 34
|
||||
array( 6, 30, 54, 78, 102, 126, 150), // Version 35
|
||||
array( 6, 24, 50, 76, 102, 128, 154), // Version 36
|
||||
array( 6, 28, 54, 80, 106, 132, 158), // Version 37
|
||||
array( 6, 32, 58, 84, 110, 136, 162), // Version 38
|
||||
array( 6, 26, 54, 82, 110, 138, 166), // Version 39
|
||||
array( 6, 30, 58, 86, 114, 142, 170), // Version 40
|
||||
);
|
||||
|
||||
/**
|
||||
* Type information coordinates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $typeInfoCoordinates = array(
|
||||
array(8, 0),
|
||||
array(8, 1),
|
||||
array(8, 2),
|
||||
array(8, 3),
|
||||
array(8, 4),
|
||||
array(8, 5),
|
||||
array(8, 7),
|
||||
array(8, 8),
|
||||
array(7, 8),
|
||||
array(5, 8),
|
||||
array(4, 8),
|
||||
array(3, 8),
|
||||
array(2, 8),
|
||||
array(1, 8),
|
||||
array(0, 8),
|
||||
);
|
||||
|
||||
/**
|
||||
* Version information polynomial.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected static $versionInfoPoly = 0x1f25;
|
||||
|
||||
/**
|
||||
* Type information polynomial.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected static $typeInfoPoly = 0x537;
|
||||
|
||||
/**
|
||||
* Type information mask pattern.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected static $typeInfoMaskPattern = 0x5412;
|
||||
|
||||
/**
|
||||
* Clears a given matrix.
|
||||
*
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
*/
|
||||
public static function clearMatrix(ByteMatrix $matrix)
|
||||
{
|
||||
$matrix->clear(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a complete matrix.
|
||||
*
|
||||
* @param BitArray $dataBits
|
||||
* @param ErrorCorrectionLevel $level
|
||||
* @param Version $version
|
||||
* @param integer $maskPattern
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
*/
|
||||
public static function buildMatrix(
|
||||
BitArray $dataBits,
|
||||
ErrorCorrectionLevel $level,
|
||||
Version $version,
|
||||
$maskPattern,
|
||||
ByteMatrix $matrix
|
||||
) {
|
||||
self::clearMatrix($matrix);
|
||||
self::embedBasicPatterns($version, $matrix);
|
||||
self::embedTypeInfo($level, $maskPattern, $matrix);
|
||||
self::maybeEmbedVersionInfo($version, $matrix);
|
||||
self::embedDataBits($dataBits, $maskPattern, $matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds type information into a matrix.
|
||||
*
|
||||
* @param ErrorCorrectionLevel $level
|
||||
* @param integer $maskPattern
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
*/
|
||||
protected static function embedTypeInfo(ErrorCorrectionLevel $level, $maskPattern, ByteMatrix $matrix)
|
||||
{
|
||||
$typeInfoBits = new BitArray();
|
||||
self::makeTypeInfoBits($level, $maskPattern, $typeInfoBits);
|
||||
|
||||
$typeInfoBitsSize = $typeInfoBits->getSize();
|
||||
|
||||
for ($i = 0; $i < $typeInfoBitsSize; $i++) {
|
||||
$bit = $typeInfoBits->get($typeInfoBitsSize - 1 - $i);
|
||||
|
||||
$x1 = self::$typeInfoCoordinates[$i][0];
|
||||
$y1 = self::$typeInfoCoordinates[$i][1];
|
||||
|
||||
$matrix->set($x1, $y1, $bit);
|
||||
|
||||
if ($i < 8) {
|
||||
$x2 = $matrix->getWidth() - $i - 1;
|
||||
$y2 = 8;
|
||||
} else {
|
||||
$x2 = 8;
|
||||
$y2 = $matrix->getHeight() - 7 + ($i - 8);
|
||||
}
|
||||
|
||||
$matrix->set($x2, $y2, $bit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates type information bits and appends them to a bit array.
|
||||
*
|
||||
* @param ErrorCorrectionLevel $level
|
||||
* @param integer $maskPattern
|
||||
* @param BitArray $bits
|
||||
* @return void
|
||||
* @throws Exception\RuntimeException
|
||||
*/
|
||||
protected static function makeTypeInfoBits(ErrorCorrectionLevel $level, $maskPattern, BitArray $bits)
|
||||
{
|
||||
$typeInfo = ($level->get() << 3) | $maskPattern;
|
||||
$bits->appendBits($typeInfo, 5);
|
||||
|
||||
$bchCode = self::calculateBchCode($typeInfo, self::$typeInfoPoly);
|
||||
$bits->appendBits($bchCode, 10);
|
||||
|
||||
$maskBits = new BitArray();
|
||||
$maskBits->appendBits(self::$typeInfoMaskPattern, 15);
|
||||
$bits->xorBits($maskBits);
|
||||
|
||||
if ($bits->getSize() !== 15) {
|
||||
throw new Exception\RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds version information if required.
|
||||
*
|
||||
* @param Version $version
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
*/
|
||||
protected static function maybeEmbedVersionInfo(Version $version, ByteMatrix $matrix)
|
||||
{
|
||||
if ($version->getVersionNumber() < 7) {
|
||||
return;
|
||||
}
|
||||
|
||||
$versionInfoBits = new BitArray();
|
||||
self::makeVersionInfoBits($version, $versionInfoBits);
|
||||
|
||||
$bitIndex = 6 * 3 - 1;
|
||||
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
for ($j = 0; $j < 3; $j++) {
|
||||
$bit = $versionInfoBits->get($bitIndex);
|
||||
$bitIndex--;
|
||||
|
||||
$matrix->set($i, $matrix->getHeight() - 11 + $j, $bit);
|
||||
$matrix->set($matrix->getHeight() - 11 + $j, $i, $bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates version information bits and appends them to a bit array.
|
||||
*
|
||||
* @param Version $version
|
||||
* @param BitArray $bits
|
||||
* @return void
|
||||
* @throws Exception\RuntimeException
|
||||
*/
|
||||
protected static function makeVersionInfoBits(Version $version, BitArray $bits)
|
||||
{
|
||||
$bits->appendBits($version->getVersionNumber(), 6);
|
||||
|
||||
$bchCode = self::calculateBchCode($version->getVersionNumber(), self::$versionInfoPoly);
|
||||
$bits->appendBits($bchCode, 12);
|
||||
|
||||
if ($bits->getSize() !== 18) {
|
||||
throw new Exception\RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the BHC code for a value and a polynomial.
|
||||
*
|
||||
* @param integer $value
|
||||
* @param integer $poly
|
||||
* @return integer
|
||||
*/
|
||||
protected static function calculateBchCode($value, $poly)
|
||||
{
|
||||
$msbSetInPoly = self::findMsbSet($poly);
|
||||
$value <<= $msbSetInPoly - 1;
|
||||
|
||||
while (self::findMsbSet($value) >= $msbSetInPoly) {
|
||||
$value ^= $poly << (self::findMsbSet($value) - $msbSetInPoly);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and MSB set.
|
||||
*
|
||||
* @param integer $value
|
||||
* @return integer
|
||||
*/
|
||||
protected static function findMsbSet($value)
|
||||
{
|
||||
$numDigits = 0;
|
||||
|
||||
while ($value !== 0) {
|
||||
$value >>= 1;
|
||||
$numDigits++;
|
||||
}
|
||||
|
||||
return $numDigits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds basic patterns into a matrix.
|
||||
*
|
||||
* @param Version $version
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
*/
|
||||
protected static function embedBasicPatterns(Version $version, ByteMatrix $matrix)
|
||||
{
|
||||
self::embedPositionDetectionPatternsAndSeparators($matrix);
|
||||
self::embedDarkDotAtLeftBottomCorner($matrix);
|
||||
self::maybeEmbedPositionAdjustmentPatterns($version, $matrix);
|
||||
self::embedTimingPatterns($matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds position detection patterns and separators into a byte matrix.
|
||||
*
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
*/
|
||||
protected static function embedPositionDetectionPatternsAndSeparators(ByteMatrix $matrix)
|
||||
{
|
||||
$pdpWidth = count(self::$positionDetectionPattern[0]);
|
||||
|
||||
self::embedPositionDetectionPattern(0, 0, $matrix);
|
||||
self::embedPositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix);
|
||||
self::embedPositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix);
|
||||
|
||||
$hspWidth = 8;
|
||||
|
||||
self::embedHorizontalSeparationPattern(0, $hspWidth - 1, $matrix);
|
||||
self::embedHorizontalSeparationPattern($matrix->getWidth() - $hspWidth, $hspWidth - 1, $matrix);
|
||||
self::embedHorizontalSeparationPattern(0, $matrix->getWidth() - $hspWidth, $matrix);
|
||||
|
||||
$vspSize = 7;
|
||||
|
||||
self::embedVerticalSeparationPattern($vspSize, 0, $matrix);
|
||||
self::embedVerticalSeparationPattern($matrix->getHeight() - $vspSize - 1, 0, $matrix);
|
||||
self::embedVerticalSeparationPattern($vspSize, $matrix->getHeight() - $vspSize, $matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a single position detection pattern into a byte matrix.
|
||||
*
|
||||
* @param integer $xStart
|
||||
* @param integer $yStart
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
*/
|
||||
protected static function embedPositionDetectionPattern($xStart, $yStart, ByteMatrix $matrix)
|
||||
{
|
||||
for ($y = 0; $y < 7; $y++) {
|
||||
for ($x = 0; $x < 7; $x++) {
|
||||
$matrix->set($xStart + $x, $yStart + $y, self::$positionDetectionPattern[$y][$x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a single horizontal separation pattern.
|
||||
*
|
||||
* @param integer $xStart
|
||||
* @param integer $yStart
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
* @throws Exception\RuntimeException
|
||||
*/
|
||||
protected static function embedHorizontalSeparationPattern($xStart, $yStart, ByteMatrix $matrix)
|
||||
{
|
||||
for ($x = 0; $x < 8; $x++) {
|
||||
if ($matrix->get($xStart + $x, $yStart) !== -1) {
|
||||
throw new Exception\RuntimeException('Byte already set');
|
||||
}
|
||||
|
||||
$matrix->set($xStart + $x, $yStart, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a single vertical separation pattern.
|
||||
*
|
||||
* @param integer $xStart
|
||||
* @param integer $yStart
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
* @throws Exception\RuntimeException
|
||||
*/
|
||||
protected static function embedVerticalSeparationPattern($xStart, $yStart, ByteMatrix $matrix)
|
||||
{
|
||||
for ($y = 0; $y < 7; $y++) {
|
||||
if ($matrix->get($xStart, $yStart + $y) !== -1) {
|
||||
throw new Exception\RuntimeException('Byte already set');
|
||||
}
|
||||
|
||||
$matrix->set($xStart, $yStart + $y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a dot at the left bottom corner.
|
||||
*
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
* @throws Exception\RuntimeException
|
||||
*/
|
||||
protected static function embedDarkDotAtLeftBottomCorner(ByteMatrix $matrix)
|
||||
{
|
||||
if ($matrix->get(8, $matrix->getHeight() - 8) === 0) {
|
||||
throw new Exception\RuntimeException('Byte already set to 0');
|
||||
}
|
||||
|
||||
$matrix->set(8, $matrix->getHeight() - 8, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds position adjustment patterns if required.
|
||||
*
|
||||
* @param Version $version
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
*/
|
||||
protected static function maybeEmbedPositionAdjustmentPatterns(Version $version, ByteMatrix $matrix)
|
||||
{
|
||||
if ($version->getVersionNumber() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
$index = $version->getVersionNumber() - 1;
|
||||
|
||||
$coordinates = self::$positionAdjustmentPatternCoordinateTable[$index];
|
||||
$numCoordinates = count($coordinates);
|
||||
|
||||
for ($i = 0; $i < $numCoordinates; $i++) {
|
||||
for ($j = 0; $j < $numCoordinates; $j++) {
|
||||
$y = $coordinates[$i];
|
||||
$x = $coordinates[$j];
|
||||
|
||||
if ($x === null || $y === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($matrix->get($x, $y) === -1) {
|
||||
self::embedPositionAdjustmentPattern($x - 2, $y - 2, $matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a single position adjustment pattern.
|
||||
*
|
||||
* @param integer $xStart
|
||||
* @param intger $yStart
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
*/
|
||||
protected static function embedPositionAdjustmentPattern($xStart, $yStart, ByteMatrix $matrix)
|
||||
{
|
||||
for ($y = 0; $y < 5; $y++) {
|
||||
for ($x = 0; $x < 5; $x++) {
|
||||
$matrix->set($xStart + $x, $yStart + $y, self::$positionAdjustmentPattern[$y][$x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds timing patterns into a matrix.
|
||||
*
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
*/
|
||||
protected static function embedTimingPatterns(ByteMatrix $matrix)
|
||||
{
|
||||
$matrixWidth = $matrix->getWidth();
|
||||
|
||||
for ($i = 8; $i < $matrixWidth - 8; $i++) {
|
||||
$bit = ($i + 1) % 2;
|
||||
|
||||
if ($matrix->get($i, 6) === -1) {
|
||||
$matrix->set($i, 6, $bit);
|
||||
}
|
||||
|
||||
if ($matrix->get(6, $i) === -1) {
|
||||
$matrix->set(6, $i, $bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds "dataBits" using "getMaskPattern".
|
||||
*
|
||||
* For debugging purposes, it skips masking process if "getMaskPattern" is
|
||||
* -1. See 8.7 of JISX0510:2004 (p.38) for how to embed data bits.
|
||||
*
|
||||
* @param BitArray $dataBits
|
||||
* @param integer $maskPattern
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
* @throws Exception\WriterException
|
||||
*/
|
||||
protected static function embedDataBits(BitArray $dataBits, $maskPattern, ByteMatrix $matrix)
|
||||
{
|
||||
$bitIndex = 0;
|
||||
$direction = -1;
|
||||
|
||||
// Start from the right bottom cell.
|
||||
$x = $matrix->getWidth() - 1;
|
||||
$y = $matrix->getHeight() - 1;
|
||||
|
||||
while ($x > 0) {
|
||||
// Skip vertical timing pattern.
|
||||
if ($x === 6) {
|
||||
$x--;
|
||||
}
|
||||
|
||||
while ($y >= 0 && $y < $matrix->getHeight()) {
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$xx = $x - $i;
|
||||
|
||||
// Skip the cell if it's not empty.
|
||||
if ($matrix->get($xx, $y) !== -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($bitIndex < $dataBits->getSize()) {
|
||||
$bit = $dataBits->get($bitIndex);
|
||||
$bitIndex++;
|
||||
} else {
|
||||
// Padding bit. If there is no bit left, we'll fill the
|
||||
// left cells with 0, as described in 8.4.9 of
|
||||
// JISX0510:2004 (p. 24).
|
||||
$bit = false;
|
||||
}
|
||||
|
||||
// Skip masking if maskPattern is -1.
|
||||
if ($maskPattern !== -1 && MaskUtil::getDataMaskBit($maskPattern, $xx, $y)) {
|
||||
$bit = !$bit;
|
||||
}
|
||||
|
||||
$matrix->set($xx, $y, $bit);
|
||||
}
|
||||
|
||||
$y += $direction;
|
||||
}
|
||||
|
||||
$direction = -$direction;
|
||||
$y += $direction;
|
||||
$x -= 2;
|
||||
}
|
||||
|
||||
// All bits should be consumed
|
||||
if ($bitIndex !== $dataBits->getSize()) {
|
||||
throw new Exception\WriterException('Not all bits consumed (' . $bitIndex . ' out of ' . $dataBits->getSize() .')');
|
||||
}
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Encoder;
|
||||
|
||||
use BaconQrCode\Common\ErrorCorrectionLevel;
|
||||
use BaconQrCode\Common\Mode;
|
||||
use BaconQrCode\Common\Version;
|
||||
|
||||
/**
|
||||
* QR code.
|
||||
*/
|
||||
class QrCode
|
||||
{
|
||||
/**
|
||||
* Number of possible mask patterns.
|
||||
*/
|
||||
const NUM_MASK_PATTERNS = 8;
|
||||
|
||||
/**
|
||||
* Mode of the QR code.
|
||||
*
|
||||
* @var Mode
|
||||
*/
|
||||
protected $mode;
|
||||
|
||||
/**
|
||||
* EC level of the QR code.
|
||||
*
|
||||
* @var ErrorCorrectionLevel
|
||||
*/
|
||||
protected $errorCorrectionLevel;
|
||||
|
||||
/**
|
||||
* Version of the QR code.
|
||||
*
|
||||
* @var Version
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
/**
|
||||
* Mask pattern of the QR code.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $maskPattern = -1;
|
||||
|
||||
/**
|
||||
* Matrix of the QR code.
|
||||
*
|
||||
* @var ByteMatrix
|
||||
*/
|
||||
protected $matrix;
|
||||
|
||||
/**
|
||||
* Gets the mode.
|
||||
*
|
||||
* @return Mode
|
||||
*/
|
||||
public function getMode()
|
||||
{
|
||||
return $this->mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mode.
|
||||
*
|
||||
* @param Mode $mode
|
||||
* @return void
|
||||
*/
|
||||
public function setMode(Mode $mode)
|
||||
{
|
||||
$this->mode = $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the EC level.
|
||||
*
|
||||
* @return ErrorCorrectionLevel
|
||||
*/
|
||||
public function getErrorCorrectionLevel()
|
||||
{
|
||||
return $this->errorCorrectionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the EC level.
|
||||
*
|
||||
* @param ErrorCorrectionLevel $errorCorrectionLevel
|
||||
* @return void
|
||||
*/
|
||||
public function setErrorCorrectionLevel(ErrorCorrectionLevel $errorCorrectionLevel)
|
||||
{
|
||||
$this->errorCorrectionLevel = $errorCorrectionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version.
|
||||
*
|
||||
* @return Version
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the version.
|
||||
*
|
||||
* @param Version $version
|
||||
* @return void
|
||||
*/
|
||||
public function setVersion(Version $version)
|
||||
{
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mask pattern.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getMaskPattern()
|
||||
{
|
||||
return $this->maskPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mask pattern.
|
||||
*
|
||||
* @param integer $maskPattern
|
||||
* @return void
|
||||
*/
|
||||
public function setMaskPattern($maskPattern)
|
||||
{
|
||||
$this->maskPattern = $maskPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the matrix.
|
||||
*
|
||||
* @return ByteMatrix
|
||||
*/
|
||||
public function getMatrix()
|
||||
{
|
||||
return $this->matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the matrix.
|
||||
*
|
||||
* @param ByteMatrix $matrix
|
||||
* @return void
|
||||
*/
|
||||
public function setMatrix(ByteMatrix $matrix)
|
||||
{
|
||||
$this->matrix = $matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether a mask pattern is valid.
|
||||
*
|
||||
* @param integer $maskPattern
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isValidMaskPattern($maskPattern)
|
||||
{
|
||||
return $maskPattern > 0 && $maskPattern < self::NUM_MASK_PATTERNS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the QR code.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$result = "<<\n"
|
||||
. " mode: " . $this->mode . "\n"
|
||||
. " ecLevel: " . $this->errorCorrectionLevel . "\n"
|
||||
. " version: " . $this->version . "\n"
|
||||
. " maskPattern: " . $this->maskPattern . "\n";
|
||||
|
||||
if ($this->matrix === null) {
|
||||
$result .= " matrix: null\n";
|
||||
} else {
|
||||
$result .= " matrix:\n";
|
||||
$result .= $this->matrix;
|
||||
}
|
||||
|
||||
$result .= ">>\n";
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
interface ExceptionInterface
|
||||
{
|
||||
}
|
||||
user/plugins/admin/vendor/bacon/bacon-qr-code/src/BaconQrCode/Exception/InvalidArgumentException.php
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
user/plugins/admin/vendor/bacon/bacon-qr-code/src/BaconQrCode/Exception/UnexpectedValueException.php
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Exception;
|
||||
|
||||
class WriterException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Color;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
|
||||
/**
|
||||
* CMYK color.
|
||||
*/
|
||||
class Cmyk implements ColorInterface
|
||||
{
|
||||
/**
|
||||
* Cyan value.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $cyan;
|
||||
|
||||
/**
|
||||
* Magenta value.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $magenta;
|
||||
|
||||
/**
|
||||
* Yellow value.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $yellow;
|
||||
|
||||
/**
|
||||
* Black value.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $black;
|
||||
|
||||
/**
|
||||
* Creates a new CMYK color.
|
||||
*
|
||||
* @param integer $cyan
|
||||
* @param integer $magenta
|
||||
* @param integer $yellow
|
||||
* @param integer $black
|
||||
*/
|
||||
public function __construct($cyan, $magenta, $yellow, $black)
|
||||
{
|
||||
if ($cyan < 0 || $cyan > 100) {
|
||||
throw new Exception\InvalidArgumentException('Cyan must be between 0 and 100');
|
||||
}
|
||||
|
||||
if ($magenta < 0 || $magenta > 100) {
|
||||
throw new Exception\InvalidArgumentException('Magenta must be between 0 and 100');
|
||||
}
|
||||
|
||||
if ($yellow < 0 || $yellow > 100) {
|
||||
throw new Exception\InvalidArgumentException('Yellow must be between 0 and 100');
|
||||
}
|
||||
|
||||
if ($black < 0 || $black > 100) {
|
||||
throw new Exception\InvalidArgumentException('Black must be between 0 and 100');
|
||||
}
|
||||
|
||||
$this->cyan = (int) $cyan;
|
||||
$this->magenta = (int) $magenta;
|
||||
$this->yellow = (int) $yellow;
|
||||
$this->black = (int) $black;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cyan value.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getCyan()
|
||||
{
|
||||
return $this->cyan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the magenta value.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getMagenta()
|
||||
{
|
||||
return $this->magenta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the yellow value.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getYellow()
|
||||
{
|
||||
return $this->yellow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the black value.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getBlack()
|
||||
{
|
||||
return $this->black;
|
||||
}
|
||||
|
||||
/**
|
||||
* toRgb(): defined by ColorInterface.
|
||||
*
|
||||
* @see ColorInterface::toRgb()
|
||||
* @return Rgb
|
||||
*/
|
||||
public function toRgb()
|
||||
{
|
||||
$k = $this->black / 100;
|
||||
$c = (-$k * $this->cyan + $k * 100 + $this->cyan) / 100;
|
||||
$m = (-$k * $this->magenta + $k * 100 + $this->magenta) / 100;
|
||||
$y = (-$k * $this->yellow + $k * 100 + $this->yellow) / 100;
|
||||
|
||||
return new Rgb(
|
||||
-$c * 255 + 255,
|
||||
-$m * 255 + 255,
|
||||
-$y * 255 + 255
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* toCmyk(): defined by ColorInterface.
|
||||
*
|
||||
* @see ColorInterface::toCmyk()
|
||||
* @return Cmyk
|
||||
*/
|
||||
public function toCmyk()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* toGray(): defined by ColorInterface.
|
||||
*
|
||||
* @see ColorInterface::toGray()
|
||||
* @return Gray
|
||||
*/
|
||||
public function toGray()
|
||||
{
|
||||
return $this->toRgb()->toGray();
|
||||
}
|
||||
}
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Color;
|
||||
|
||||
/**
|
||||
* Color interface.
|
||||
*/
|
||||
interface ColorInterface
|
||||
{
|
||||
/**
|
||||
* Converts the color to RGB.
|
||||
*
|
||||
* @return Rgb
|
||||
*/
|
||||
public function toRgb();
|
||||
|
||||
/**
|
||||
* Converts the color to CMYK.
|
||||
*
|
||||
* @return Cmyk
|
||||
*/
|
||||
public function toCmyk();
|
||||
|
||||
/**
|
||||
* Converts the color to gray.
|
||||
*
|
||||
* @return Gray
|
||||
*/
|
||||
public function toGray();
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Color;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
|
||||
/**
|
||||
* Gray color.
|
||||
*/
|
||||
class Gray implements ColorInterface
|
||||
{
|
||||
/**
|
||||
* Gray value.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $gray;
|
||||
|
||||
/**
|
||||
* Creates a new gray color.
|
||||
*
|
||||
* A low gray value means black, while a high value means white.
|
||||
*
|
||||
* @param integer $gray
|
||||
*/
|
||||
public function __construct($gray)
|
||||
{
|
||||
if ($gray < 0 || $gray > 100) {
|
||||
throw new Exception\InvalidArgumentException('Gray must be between 0 and 100');
|
||||
}
|
||||
|
||||
$this->gray = (int) $gray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the gray value.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getGray()
|
||||
{
|
||||
return $this->gray;
|
||||
}
|
||||
|
||||
/**
|
||||
* toRgb(): defined by ColorInterface.
|
||||
*
|
||||
* @see ColorInterface::toRgb()
|
||||
* @return Rgb
|
||||
*/
|
||||
public function toRgb()
|
||||
{
|
||||
return new Rgb($this->gray * 2.55, $this->gray * 2.55, $this->gray * 2.55);
|
||||
}
|
||||
|
||||
/**
|
||||
* toCmyk(): defined by ColorInterface.
|
||||
*
|
||||
* @see ColorInterface::toCmyk()
|
||||
* @return Cmyk
|
||||
*/
|
||||
public function toCmyk()
|
||||
{
|
||||
return new Cmyk(0, 0, 0, 100 - $this->gray);
|
||||
}
|
||||
|
||||
/**
|
||||
* toGray(): defined by ColorInterface.
|
||||
*
|
||||
* @see ColorInterface::toGray()
|
||||
* @return Gray
|
||||
*/
|
||||
public function toGray()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Color;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
|
||||
/**
|
||||
* RGB color.
|
||||
*/
|
||||
class Rgb implements ColorInterface
|
||||
{
|
||||
/**
|
||||
* Red value.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $red;
|
||||
|
||||
/**
|
||||
* Green value.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $green;
|
||||
|
||||
/**
|
||||
* Blue value.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $blue;
|
||||
|
||||
/**
|
||||
* Creates a new RGB color.
|
||||
*
|
||||
* @param integer $red
|
||||
* @param integer $green
|
||||
* @param integer $blue
|
||||
*/
|
||||
public function __construct($red, $green, $blue)
|
||||
{
|
||||
if ($red < 0 || $red > 255) {
|
||||
throw new Exception\InvalidArgumentException('Red must be between 0 and 255');
|
||||
}
|
||||
|
||||
if ($green < 0 || $green > 255) {
|
||||
throw new Exception\InvalidArgumentException('Green must be between 0 and 255');
|
||||
}
|
||||
|
||||
if ($blue < 0 || $blue > 255) {
|
||||
throw new Exception\InvalidArgumentException('Blue must be between 0 and 255');
|
||||
}
|
||||
|
||||
$this->red = (int) $red;
|
||||
$this->green = (int) $green;
|
||||
$this->blue = (int) $blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the red value.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getRed()
|
||||
{
|
||||
return $this->red;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the green value.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getGreen()
|
||||
{
|
||||
return $this->green;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the blue value.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getBlue()
|
||||
{
|
||||
return $this->blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hexadecimal string representation of the RGB value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return sprintf('%02x%02x%02x', $this->red, $this->green, $this->blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* toRgb(): defined by ColorInterface.
|
||||
*
|
||||
* @see ColorInterface::toRgb()
|
||||
* @return Rgb
|
||||
*/
|
||||
public function toRgb()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* toCmyk(): defined by ColorInterface.
|
||||
*
|
||||
* @see ColorInterface::toCmyk()
|
||||
* @return Cmyk
|
||||
*/
|
||||
public function toCmyk()
|
||||
{
|
||||
$c = 1 - ($this->red / 255);
|
||||
$m = 1 - ($this->green / 255);
|
||||
$y = 1 - ($this->blue / 255);
|
||||
$k = min($c, $m, $y);
|
||||
|
||||
return new Cmyk(
|
||||
100 * ($c - $k) / (1 - $k),
|
||||
100 * ($m - $k) / (1 - $k),
|
||||
100 * ($y - $k) / (1 - $k),
|
||||
100 * $k
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* toGray(): defined by ColorInterface.
|
||||
*
|
||||
* @see ColorInterface::toGray()
|
||||
* @return Gray
|
||||
*/
|
||||
public function toGray()
|
||||
{
|
||||
return new Gray(($this->red * 0.21 + $this->green * 0.71 + $this->blue * 0.07) / 2.55);
|
||||
}
|
||||
}
|
||||
Vendored
+338
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Image;
|
||||
|
||||
use BaconQrCode\Encoder\QrCode;
|
||||
use BaconQrCode\Renderer\Color;
|
||||
use BaconQrCode\Renderer\Image\Decorator\DecoratorInterface;
|
||||
use BaconQrCode\Exception;
|
||||
|
||||
/**
|
||||
* Image renderer, supporting multiple backends.
|
||||
*/
|
||||
abstract class AbstractRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* Margin around the QR code, also known as quiet zone.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $margin = 4;
|
||||
|
||||
/**
|
||||
* Requested width of the rendered image.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $width = 0;
|
||||
|
||||
/**
|
||||
* Requested height of the rendered image.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $height = 0;
|
||||
|
||||
/**
|
||||
* Whether dimensions should be rounded down.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $roundDimensions = true;
|
||||
|
||||
/**
|
||||
* Final width of the image.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $finalWidth;
|
||||
|
||||
/**
|
||||
* Final height of the image.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $finalHeight;
|
||||
|
||||
/**
|
||||
* Size of each individual block.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $blockSize;
|
||||
|
||||
/**
|
||||
* Background color.
|
||||
*
|
||||
* @var Color\ColorInterface
|
||||
*/
|
||||
protected $backgroundColor;
|
||||
|
||||
/**
|
||||
* Whether dimensions should be rounded down
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $floorToClosestDimension;
|
||||
|
||||
/**
|
||||
* Foreground color.
|
||||
*
|
||||
* @var Color\ColorInterface
|
||||
*/
|
||||
protected $foregroundColor;
|
||||
|
||||
/**
|
||||
* Decorators used on QR codes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $decorators = array();
|
||||
|
||||
/**
|
||||
* Sets the margin around the QR code.
|
||||
*
|
||||
* @param integer $margin
|
||||
* @return AbstractRenderer
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function setMargin($margin)
|
||||
{
|
||||
if ($margin < 0) {
|
||||
throw new Exception\InvalidArgumentException('Margin must be equal to greater than 0');
|
||||
}
|
||||
|
||||
$this->margin = (int) $margin;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the margin around the QR code.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getMargin()
|
||||
{
|
||||
return $this->margin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the height around the renderd image.
|
||||
*
|
||||
* If the width is smaller than the matrix width plus padding, the renderer
|
||||
* will automatically use that as the width instead of the specified one.
|
||||
*
|
||||
* @param integer $width
|
||||
* @return AbstractRenderer
|
||||
*/
|
||||
public function setWidth($width)
|
||||
{
|
||||
$this->width = (int) $width;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of the rendered image.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the height around the renderd image.
|
||||
*
|
||||
* If the height is smaller than the matrix height plus padding, the
|
||||
* renderer will automatically use that as the height instead of the
|
||||
* specified one.
|
||||
*
|
||||
* @param integer $height
|
||||
* @return AbstractRenderer
|
||||
*/
|
||||
public function setHeight($height)
|
||||
{
|
||||
$this->height = (int) $height;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height around the rendered image.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether dimensions should be rounded down.
|
||||
*
|
||||
* @param boolean $flag
|
||||
* @return AbstractRenderer
|
||||
*/
|
||||
public function setRoundDimensions($flag)
|
||||
{
|
||||
$this->floorToClosestDimension = $flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether dimensions should be rounded down.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function shouldRoundDimensions()
|
||||
{
|
||||
return $this->floorToClosestDimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets background color.
|
||||
*
|
||||
* @param Color\ColorInterface $color
|
||||
* @return AbstractRenderer
|
||||
*/
|
||||
public function setBackgroundColor(Color\ColorInterface $color)
|
||||
{
|
||||
$this->backgroundColor = $color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets background color.
|
||||
*
|
||||
* @return Color\ColorInterface
|
||||
*/
|
||||
public function getBackgroundColor()
|
||||
{
|
||||
if ($this->backgroundColor === null) {
|
||||
$this->backgroundColor = new Color\Gray(100);
|
||||
}
|
||||
|
||||
return $this->backgroundColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets foreground color.
|
||||
*
|
||||
* @param Color\ColorInterface $color
|
||||
* @return AbstractRenderer
|
||||
*/
|
||||
public function setForegroundColor(Color\ColorInterface $color)
|
||||
{
|
||||
$this->foregroundColor = $color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets foreground color.
|
||||
*
|
||||
* @return Color\ColorInterface
|
||||
*/
|
||||
public function getForegroundColor()
|
||||
{
|
||||
if ($this->foregroundColor === null) {
|
||||
$this->foregroundColor = new Color\Gray(0);
|
||||
}
|
||||
|
||||
return $this->foregroundColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a decorator to the renderer.
|
||||
*
|
||||
* @param DecoratorInterface $decorator
|
||||
* @return AbstractRenderer
|
||||
*/
|
||||
public function addDecorator(DecoratorInterface $decorator)
|
||||
{
|
||||
$this->decorators[] = $decorator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* render(): defined by RendererInterface.
|
||||
*
|
||||
* @see RendererInterface::render()
|
||||
* @param QrCode $qrCode
|
||||
* @return string
|
||||
*/
|
||||
public function render(QrCode $qrCode)
|
||||
{
|
||||
$input = $qrCode->getMatrix();
|
||||
$inputWidth = $input->getWidth();
|
||||
$inputHeight = $input->getHeight();
|
||||
$qrWidth = $inputWidth + ($this->getMargin() << 1);
|
||||
$qrHeight = $inputHeight + ($this->getMargin() << 1);
|
||||
$outputWidth = max($this->getWidth(), $qrWidth);
|
||||
$outputHeight = max($this->getHeight(), $qrHeight);
|
||||
$multiple = (int) min($outputWidth / $qrWidth, $outputHeight / $qrHeight);
|
||||
|
||||
if ($this->shouldRoundDimensions()) {
|
||||
$outputWidth -= $outputWidth % $multiple;
|
||||
$outputHeight -= $outputHeight % $multiple;
|
||||
}
|
||||
|
||||
// Padding includes both the quiet zone and the extra white pixels to
|
||||
// accommodate the requested dimensions. For example, if input is 25x25
|
||||
// the QR will be 33x33 including the quiet zone. If the requested size
|
||||
// is 200x160, the multiple will be 4, for a QR of 132x132. These will
|
||||
// handle all the padding from 100x100 (the actual QR) up to 200x160.
|
||||
$leftPadding = (int) (($outputWidth - ($inputWidth * $multiple)) / 2);
|
||||
$topPadding = (int) (($outputHeight - ($inputHeight * $multiple)) / 2);
|
||||
|
||||
// Store calculated parameters
|
||||
$this->finalWidth = $outputWidth;
|
||||
$this->finalHeight = $outputHeight;
|
||||
$this->blockSize = $multiple;
|
||||
|
||||
$this->init();
|
||||
$this->addColor('background', $this->getBackgroundColor());
|
||||
$this->addColor('foreground', $this->getForegroundColor());
|
||||
$this->drawBackground('background');
|
||||
|
||||
foreach ($this->decorators as $decorator) {
|
||||
$decorator->preProcess(
|
||||
$qrCode,
|
||||
$this,
|
||||
$outputWidth,
|
||||
$outputHeight,
|
||||
$leftPadding,
|
||||
$topPadding,
|
||||
$multiple
|
||||
);
|
||||
}
|
||||
|
||||
for ($inputY = 0, $outputY = $topPadding; $inputY < $inputHeight; $inputY++, $outputY += $multiple) {
|
||||
for ($inputX = 0, $outputX = $leftPadding; $inputX < $inputWidth; $inputX++, $outputX += $multiple) {
|
||||
if ($input->get($inputX, $inputY) === 1) {
|
||||
$this->drawBlock($outputX, $outputY, 'foreground');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->decorators as $decorator) {
|
||||
$decorator->postProcess(
|
||||
$qrCode,
|
||||
$this,
|
||||
$outputWidth,
|
||||
$outputHeight,
|
||||
$leftPadding,
|
||||
$topPadding,
|
||||
$multiple
|
||||
);
|
||||
}
|
||||
|
||||
return $this->getByteStream();
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Image\Decorator;
|
||||
|
||||
use BaconQrCode\Encoder\QrCode;
|
||||
use BaconQrCode\Renderer\Image\RendererInterface;
|
||||
|
||||
/**
|
||||
* Decorator interface.
|
||||
*/
|
||||
interface DecoratorInterface
|
||||
{
|
||||
/**
|
||||
* Pre-process a QR code.
|
||||
*
|
||||
* @param QrCode $qrCode
|
||||
* @param RendererInterface $renderer
|
||||
* @param integer $outputWidth
|
||||
* @param integer $outputHeight
|
||||
* @param integer $leftPadding
|
||||
* @param integer $topPadding
|
||||
* @param integer $multiple
|
||||
* @return void
|
||||
*/
|
||||
public function preProcess(
|
||||
QrCode $qrCode,
|
||||
RendererInterface $renderer,
|
||||
$outputWidth,
|
||||
$outputHeight,
|
||||
$leftPadding,
|
||||
$topPadding,
|
||||
$multiple
|
||||
);
|
||||
|
||||
/**
|
||||
* Post-process a QR code.
|
||||
*
|
||||
* @param QrCode $qrCode
|
||||
* @param RendererInterface $renderer
|
||||
* @param integer $outputWidth
|
||||
* @param integer $outputHeight
|
||||
* @param integer $leftPadding
|
||||
* @param integer $topPadding
|
||||
* @param integer $multiple
|
||||
* @return void
|
||||
*/
|
||||
public function postProcess(
|
||||
QrCode $qrCode,
|
||||
RendererInterface $renderer,
|
||||
$outputWidth,
|
||||
$outputHeight,
|
||||
$leftPadding,
|
||||
$topPadding,
|
||||
$multiple
|
||||
);
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Image\Decorator;
|
||||
|
||||
use BaconQrCode\Encoder\QrCode;
|
||||
use BaconQrCode\Renderer\Image\RendererInterface;
|
||||
use BaconQrCode\Renderer\Color;
|
||||
|
||||
/**
|
||||
* Finder pattern decorator.
|
||||
*/
|
||||
class FinderPattern implements DecoratorInterface
|
||||
{
|
||||
/**
|
||||
* @var Color\ColorInterface
|
||||
*/
|
||||
protected $innerColor;
|
||||
|
||||
/**
|
||||
* @varColor\ColorInterface
|
||||
*/
|
||||
protected $outerColor;
|
||||
|
||||
/**
|
||||
* Outer position detection pattern.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $outerPositionDetectionPattern = array(
|
||||
array(1, 1, 1, 1, 1, 1, 1),
|
||||
array(1, 0, 0, 0, 0, 0, 1),
|
||||
array(1, 0, 0, 0, 0, 0, 1),
|
||||
array(1, 0, 0, 0, 0, 0, 1),
|
||||
array(1, 0, 0, 0, 0, 0, 1),
|
||||
array(1, 0, 0, 0, 0, 0, 1),
|
||||
array(1, 1, 1, 1, 1, 1, 1),
|
||||
);
|
||||
|
||||
/**
|
||||
* Inner position detection pattern.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $innerPositionDetectionPattern = array(
|
||||
array(0, 0, 0, 0, 0, 0, 0),
|
||||
array(0, 0, 0, 0, 0, 0, 0),
|
||||
array(0, 0, 1, 1, 1, 0, 0),
|
||||
array(0, 0, 1, 1, 1, 0, 0),
|
||||
array(0, 0, 1, 1, 1, 0, 0),
|
||||
array(0, 0, 0, 0, 0, 0, 0),
|
||||
array(0, 0, 0, 0, 0, 0, 0),
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets outer color.
|
||||
*
|
||||
* @param Color\ColorInterface $color
|
||||
* @return FinderPattern
|
||||
*/
|
||||
public function setOuterColor(Color\ColorInterface $color)
|
||||
{
|
||||
$this->outerColor = $color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets outer color.
|
||||
*
|
||||
* @return Color\ColorInterface
|
||||
*/
|
||||
public function getOuterColor()
|
||||
{
|
||||
if ($this->outerColor === null) {
|
||||
$this->outerColor = new Color\Gray(100);
|
||||
}
|
||||
|
||||
return $this->outerColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets inner color.
|
||||
*
|
||||
* @param Color\ColorInterface $color
|
||||
* @return FinderPattern
|
||||
*/
|
||||
public function setInnerColor(Color\ColorInterface $color)
|
||||
{
|
||||
$this->innerColor = $color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets inner color.
|
||||
*
|
||||
* @return Color\ColorInterface
|
||||
*/
|
||||
public function getInnerColor()
|
||||
{
|
||||
if ($this->innerColor === null) {
|
||||
$this->innerColor = new Color\Gray(0);
|
||||
}
|
||||
|
||||
return $this->innerColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* preProcess(): defined by DecoratorInterface.
|
||||
*
|
||||
* @see DecoratorInterface::preProcess()
|
||||
* @param QrCode $qrCode
|
||||
* @param RendererInterface $renderer
|
||||
* @param integer $outputWidth
|
||||
* @param integer $outputHeight
|
||||
* @param integer $leftPadding
|
||||
* @param integer $topPadding
|
||||
* @param integer $multiple
|
||||
* @return void
|
||||
*/
|
||||
public function preProcess(
|
||||
QrCode $qrCode,
|
||||
RendererInterface $renderer,
|
||||
$outputWidth,
|
||||
$outputHeight,
|
||||
$leftPadding,
|
||||
$topPadding,
|
||||
$multiple
|
||||
) {
|
||||
$matrix = $qrCode->getMatrix();
|
||||
$positions = array(
|
||||
array(0, 0),
|
||||
array($matrix->getWidth() - 7, 0),
|
||||
array(0, $matrix->getHeight() - 7),
|
||||
);
|
||||
|
||||
foreach (self::$outerPositionDetectionPattern as $y => $row) {
|
||||
foreach ($row as $x => $isSet) {
|
||||
foreach ($positions as $position) {
|
||||
$matrix->set($x + $position[0], $y + $position[1], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* postProcess(): defined by DecoratorInterface.
|
||||
*
|
||||
* @see DecoratorInterface::postProcess()
|
||||
*
|
||||
* @param QrCode $qrCode
|
||||
* @param RendererInterface $renderer
|
||||
* @param integer $outputWidth
|
||||
* @param integer $outputHeight
|
||||
* @param integer $leftPadding
|
||||
* @param integer $topPadding
|
||||
* @param integer $multiple
|
||||
* @return void
|
||||
*/
|
||||
public function postProcess(
|
||||
QrCode $qrCode,
|
||||
RendererInterface $renderer,
|
||||
$outputWidth,
|
||||
$outputHeight,
|
||||
$leftPadding,
|
||||
$topPadding,
|
||||
$multiple
|
||||
) {
|
||||
$matrix = $qrCode->getMatrix();
|
||||
$positions = array(
|
||||
array(0, 0),
|
||||
array($matrix->getWidth() - 7, 0),
|
||||
array(0, $matrix->getHeight() - 7),
|
||||
);
|
||||
|
||||
$renderer->addColor('finder-outer', $this->getOuterColor());
|
||||
$renderer->addColor('finder-inner', $this->getInnerColor());
|
||||
|
||||
foreach (self::$outerPositionDetectionPattern as $y => $row) {
|
||||
foreach ($row as $x => $isOuterSet) {
|
||||
$isInnerSet = self::$innerPositionDetectionPattern[$y][$x];
|
||||
|
||||
if ($isOuterSet) {
|
||||
foreach ($positions as $position) {
|
||||
$renderer->drawBlock(
|
||||
$leftPadding + $x * $multiple + $position[0] * $multiple,
|
||||
$topPadding + $y * $multiple + $position[1] * $multiple,
|
||||
'finder-outer'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isInnerSet) {
|
||||
foreach ($positions as $position) {
|
||||
$renderer->drawBlock(
|
||||
$leftPadding + $x * $multiple + $position[0] * $multiple,
|
||||
$topPadding + $y * $multiple + $position[1] * $multiple,
|
||||
'finder-inner'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Image;
|
||||
|
||||
use BaconQrCode\Renderer\Color\ColorInterface;
|
||||
use BaconQrCode\Renderer\Color\Rgb;
|
||||
use BaconQrCode\Renderer\Color\Cmyk;
|
||||
use BaconQrCode\Renderer\Color\Gray;
|
||||
|
||||
/**
|
||||
* EPS backend.
|
||||
*/
|
||||
class Eps extends AbstractRenderer
|
||||
{
|
||||
/**
|
||||
* EPS string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $eps;
|
||||
|
||||
/**
|
||||
* Colors used for drawing.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $colors = array();
|
||||
|
||||
/**
|
||||
* Current color.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $currentColor;
|
||||
|
||||
/**
|
||||
* init(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::init()
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->eps = "%!PS-Adobe-3.0 EPSF-3.0\n"
|
||||
. "%%BoundingBox: 0 0 " . $this->finalWidth . " " . $this->finalHeight . "\n"
|
||||
. "/F { rectfill } def\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* addColor(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::addColor()
|
||||
* @param string $id
|
||||
* @param ColorInterface $color
|
||||
* @return void
|
||||
*/
|
||||
public function addColor($id, ColorInterface $color)
|
||||
{
|
||||
if (
|
||||
!$color instanceof Rgb
|
||||
&& !$color instanceof Cmyk
|
||||
&& !$color instanceof Gray
|
||||
) {
|
||||
$color = $color->toCmyk();
|
||||
}
|
||||
|
||||
$this->colors[$id] = $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* drawBackground(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::drawBackground()
|
||||
* @param string $colorId
|
||||
* @return void
|
||||
*/
|
||||
public function drawBackground($colorId)
|
||||
{
|
||||
$this->setColor($colorId);
|
||||
$this->eps .= "0 0 " . $this->finalWidth . " " . $this->finalHeight . " F\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* drawBlock(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::drawBlock()
|
||||
* @param integer $x
|
||||
* @param integer $y
|
||||
* @param string $colorId
|
||||
* @return void
|
||||
*/
|
||||
public function drawBlock($x, $y, $colorId)
|
||||
{
|
||||
$this->setColor($colorId);
|
||||
$this->eps .= $x . " " . ($this->finalHeight - $y) . " " . $this->blockSize . " " . $this->blockSize . " F\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* getByteStream(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::getByteStream()
|
||||
* @return string
|
||||
*/
|
||||
public function getByteStream()
|
||||
{
|
||||
return $this->eps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets color to use.
|
||||
*
|
||||
* @param string $colorId
|
||||
* @return void
|
||||
*/
|
||||
protected function setColor($colorId)
|
||||
{
|
||||
if ($colorId !== $this->currentColor) {
|
||||
$color = $this->colors[$colorId];
|
||||
|
||||
if ($color instanceof Rgb) {
|
||||
$this->eps .= sprintf(
|
||||
"%F %F %F setrgbcolor\n",
|
||||
$color->getRed() / 100,
|
||||
$color->getGreen() / 100,
|
||||
$color->getBlue() / 100
|
||||
);
|
||||
} elseif ($color instanceof Cmyk) {
|
||||
$this->eps .= sprintf(
|
||||
"%F %F %F %F setcmykcolor\n",
|
||||
$color->getCyan() / 100,
|
||||
$color->getMagenta() / 100,
|
||||
$color->getYellow() / 100,
|
||||
$color->getBlack() / 100
|
||||
);
|
||||
} elseif ($color instanceof Gray) {
|
||||
$this->eps .= sprintf(
|
||||
"%F setgray\n",
|
||||
$color->getGray() / 100
|
||||
);
|
||||
}
|
||||
|
||||
$this->currentColor = $colorId;
|
||||
}
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Image;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
use BaconQrCode\Renderer\Color\ColorInterface;
|
||||
|
||||
/**
|
||||
* PNG backend.
|
||||
*/
|
||||
class Png extends AbstractRenderer
|
||||
{
|
||||
/**
|
||||
* Image resource used when drawing.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $image;
|
||||
|
||||
/**
|
||||
* Colors used for drawing.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $colors = array();
|
||||
|
||||
/**
|
||||
* init(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::init()
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->image = imagecreatetruecolor($this->finalWidth, $this->finalHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* addColor(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::addColor()
|
||||
* @param string $id
|
||||
* @param ColorInterface $color
|
||||
* @return void
|
||||
* @throws Exception\RuntimeException
|
||||
*/
|
||||
public function addColor($id, ColorInterface $color)
|
||||
{
|
||||
if ($this->image === null) {
|
||||
throw new Exception\RuntimeException('Colors can only be added after init');
|
||||
}
|
||||
|
||||
$color = $color->toRgb();
|
||||
|
||||
$this->colors[$id] = imagecolorallocate(
|
||||
$this->image,
|
||||
$color->getRed(),
|
||||
$color->getGreen(),
|
||||
$color->getBlue()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* drawBackground(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::drawBackground()
|
||||
* @param string $colorId
|
||||
* @return void
|
||||
*/
|
||||
public function drawBackground($colorId)
|
||||
{
|
||||
imagefill($this->image, 0, 0, $this->colors[$colorId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* drawBlock(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::drawBlock()
|
||||
* @param integer $x
|
||||
* @param integer $y
|
||||
* @param string $colorId
|
||||
* @return void
|
||||
*/
|
||||
public function drawBlock($x, $y, $colorId)
|
||||
{
|
||||
imagefilledrectangle(
|
||||
$this->image,
|
||||
$x,
|
||||
$y,
|
||||
$x + $this->blockSize - 1,
|
||||
$y + $this->blockSize - 1,
|
||||
$this->colors[$colorId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* getByteStream(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::getByteStream()
|
||||
* @return string
|
||||
*/
|
||||
public function getByteStream()
|
||||
{
|
||||
ob_start();
|
||||
imagepng($this->image);
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Image;
|
||||
|
||||
use BaconQrCode\Renderer\Color\ColorInterface;
|
||||
use BaconQrCode\Renderer\RendererInterface as GeneralRendererInterface;
|
||||
|
||||
/**
|
||||
* Renderer interface.
|
||||
*/
|
||||
interface RendererInterface extends GeneralRendererInterface
|
||||
{
|
||||
/**
|
||||
* Initiates the drawing area.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init();
|
||||
|
||||
/**
|
||||
* Adds a color to the drawing area.
|
||||
*
|
||||
* @param string $id
|
||||
* @param ColorInterface $color
|
||||
* @return void
|
||||
*/
|
||||
public function addColor($id, ColorInterface $color);
|
||||
|
||||
/**
|
||||
* Draws the background.
|
||||
*
|
||||
* @param string $colorId
|
||||
* @return void
|
||||
*/
|
||||
public function drawBackground($colorId);
|
||||
|
||||
/**
|
||||
* Draws a block at a specified position.
|
||||
*
|
||||
* @param integer $x
|
||||
* @param integer $y
|
||||
* @param string $colorId
|
||||
* @return void
|
||||
*/
|
||||
public function drawBlock($x, $y, $colorId);
|
||||
|
||||
/**
|
||||
* Returns the byte stream representing the QR code.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getByteStream();
|
||||
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Image;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
use BaconQrCode\Renderer\Color\ColorInterface;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* SVG backend.
|
||||
*/
|
||||
class Svg extends AbstractRenderer
|
||||
{
|
||||
/**
|
||||
* SVG resource.
|
||||
*
|
||||
* @var SimpleXMLElement
|
||||
*/
|
||||
protected $svg;
|
||||
|
||||
/**
|
||||
* Colors used for drawing.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $colors = array();
|
||||
|
||||
/**
|
||||
* Prototype IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $prototypeIds = array();
|
||||
|
||||
/**
|
||||
* init(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::init()
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->svg = new SimpleXMLElement(
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
. '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"/>'
|
||||
);
|
||||
$this->svg->addAttribute('version', '1.1');
|
||||
$this->svg->addAttribute('width', $this->finalWidth . 'px');
|
||||
$this->svg->addAttribute('height', $this->finalHeight . 'px');
|
||||
$this->svg->addAttribute('viewBox', '0 0 ' . $this->finalWidth . ' ' . $this->finalHeight);
|
||||
$this->svg->addChild('defs');
|
||||
}
|
||||
|
||||
/**
|
||||
* addColor(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::addColor()
|
||||
* @param string $id
|
||||
* @param ColorInterface $color
|
||||
* @return void
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function addColor($id, ColorInterface $color)
|
||||
{
|
||||
$this->colors[$id] = (string) $color->toRgb();
|
||||
}
|
||||
|
||||
/**
|
||||
* drawBackground(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::drawBackground()
|
||||
* @param string $colorId
|
||||
* @return void
|
||||
*/
|
||||
public function drawBackground($colorId)
|
||||
{
|
||||
$rect = $this->svg->addChild('rect');
|
||||
$rect->addAttribute('x', 0);
|
||||
$rect->addAttribute('y', 0);
|
||||
$rect->addAttribute('width', $this->finalWidth);
|
||||
$rect->addAttribute('height', $this->finalHeight);
|
||||
$rect->addAttribute('fill', '#' . $this->colors[$colorId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* drawBlock(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::drawBlock()
|
||||
* @param integer $x
|
||||
* @param integer $y
|
||||
* @param string $colorId
|
||||
* @return void
|
||||
*/
|
||||
public function drawBlock($x, $y, $colorId)
|
||||
{
|
||||
$use = $this->svg->addChild('use');
|
||||
$use->addAttribute('x', $x);
|
||||
$use->addAttribute('y', $y);
|
||||
$use->addAttribute(
|
||||
'xlink:href',
|
||||
$this->getRectPrototypeId($colorId),
|
||||
'http://www.w3.org/1999/xlink'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* getByteStream(): defined by RendererInterface.
|
||||
*
|
||||
* @see ImageRendererInterface::getByteStream()
|
||||
* @return string
|
||||
*/
|
||||
public function getByteStream()
|
||||
{
|
||||
return $this->svg->asXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prototype ID for a color.
|
||||
*
|
||||
* @param integer $colorId
|
||||
* @return string
|
||||
*/
|
||||
protected function getRectPrototypeId($colorId)
|
||||
{
|
||||
if (!isset($this->prototypeIds[$colorId])) {
|
||||
$id = 'r' . dechex(count($this->prototypeIds));
|
||||
|
||||
$rect = $this->svg->defs->addChild('rect');
|
||||
$rect->addAttribute('id', $id);
|
||||
$rect->addAttribute('width', $this->blockSize);
|
||||
$rect->addAttribute('height', $this->blockSize);
|
||||
$rect->addAttribute('fill', '#' . $this->colors[$colorId]);
|
||||
|
||||
$this->prototypeIds[$colorId] = '#' . $id;
|
||||
}
|
||||
|
||||
return $this->prototypeIds[$colorId];
|
||||
}
|
||||
}
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer;
|
||||
|
||||
use BaconQrCode\Encoder\QrCode;
|
||||
|
||||
/**
|
||||
* Renderer interface.
|
||||
*/
|
||||
interface RendererInterface
|
||||
{
|
||||
/**
|
||||
* Renders a QR code.
|
||||
*
|
||||
* @param QrCode $qrCode
|
||||
* @return string
|
||||
*/
|
||||
public function render(QrCode $qrCode);
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Text;
|
||||
|
||||
use BaconQrCode\Encoder\QrCode;
|
||||
|
||||
/**
|
||||
* Html renderer.
|
||||
*/
|
||||
class Html extends Plain
|
||||
{
|
||||
/**
|
||||
* HTML CSS class attribute value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '';
|
||||
|
||||
/**
|
||||
* HTML CSS style definition for the code element.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $style = 'font-family: monospace; line-height: 0.65em; letter-spacing: -1px';
|
||||
|
||||
/**
|
||||
* Set CSS class name.
|
||||
*
|
||||
* @param string $class
|
||||
*/
|
||||
public function setClass($class)
|
||||
{
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS class name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClass()
|
||||
{
|
||||
return $this->class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set CSS style value.
|
||||
*
|
||||
* @param string $style
|
||||
*/
|
||||
public function setStyle($style)
|
||||
{
|
||||
$this->style = $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS style value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStyle()
|
||||
{
|
||||
return $this->style;
|
||||
}
|
||||
|
||||
/**
|
||||
* render(): defined by RendererInterface.
|
||||
*
|
||||
* @see RendererInterface::render()
|
||||
* @param QrCode $qrCode
|
||||
* @return string
|
||||
*/
|
||||
public function render(QrCode $qrCode)
|
||||
{
|
||||
$textCode = parent::render($qrCode);
|
||||
|
||||
$result = '<pre'
|
||||
. ' style="' . htmlspecialchars($this->style, ENT_QUOTES, 'utf-8') . '"'
|
||||
. ' class="' . htmlspecialchars($this->class, ENT_QUOTES, 'utf-8') . '"'
|
||||
. '>' . $textCode . '</pre>';
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode\Renderer\Text;
|
||||
|
||||
use BaconQrCode\Exception;
|
||||
use BaconQrCode\Encoder\QrCode;
|
||||
use BaconQrCode\Renderer\RendererInterface;
|
||||
|
||||
/**
|
||||
* Plaintext renderer.
|
||||
*/
|
||||
class Plain implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* Margin around the QR code, also known as quiet zone.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $margin = 1;
|
||||
|
||||
/**
|
||||
* Char used for full block.
|
||||
*
|
||||
* UTF-8 FULL BLOCK (U+2588)
|
||||
*
|
||||
* @var string
|
||||
* @link http://www.fileformat.info/info/unicode/char/2588/index.htm
|
||||
*/
|
||||
protected $fullBlock = "\xE2\x96\x88";
|
||||
|
||||
/**
|
||||
* Char used for empty space
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $emptyBlock = ' ';
|
||||
|
||||
/**
|
||||
* Set char used as full block (occupied space, "black").
|
||||
*
|
||||
* @param string $fullBlock
|
||||
*/
|
||||
public function setFullBlock($fullBlock)
|
||||
{
|
||||
$this->fullBlock = $fullBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get char used as full block (occupied space, "black").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFullBlock()
|
||||
{
|
||||
return $this->fullBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set char used as empty block (empty space, "white").
|
||||
*
|
||||
* @param string $emptyBlock
|
||||
*/
|
||||
public function setEmptyBlock($emptyBlock)
|
||||
{
|
||||
$this->emptyBlock = $emptyBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get char used as empty block (empty space, "white").
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEmptyBlock()
|
||||
{
|
||||
return $this->emptyBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the margin around the QR code.
|
||||
*
|
||||
* @param integer $margin
|
||||
* @return AbstractRenderer
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function setMargin($margin)
|
||||
{
|
||||
if ($margin < 0) {
|
||||
throw new Exception\InvalidArgumentException('Margin must be equal to greater than 0');
|
||||
}
|
||||
|
||||
$this->margin = (int) $margin;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the margin around the QR code.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getMargin()
|
||||
{
|
||||
return $this->margin;
|
||||
}
|
||||
|
||||
/**
|
||||
* render(): defined by RendererInterface.
|
||||
*
|
||||
* @see RendererInterface::render()
|
||||
* @param QrCode $qrCode
|
||||
* @return string
|
||||
*/
|
||||
public function render(QrCode $qrCode)
|
||||
{
|
||||
$result = '';
|
||||
$matrix = $qrCode->getMatrix();
|
||||
$width = $matrix->getWidth();
|
||||
|
||||
// Top margin
|
||||
for ($x = 0; $x < $this->margin; $x++) {
|
||||
$result .= str_repeat($this->emptyBlock, $width + 2 * $this->margin)."\n";
|
||||
}
|
||||
|
||||
// Body
|
||||
$array = $matrix->getArray();
|
||||
|
||||
foreach ($array as $row) {
|
||||
$result .= str_repeat($this->emptyBlock, $this->margin); // left margin
|
||||
foreach ($row as $byte) {
|
||||
$result .= $byte ? $this->fullBlock : $this->emptyBlock;
|
||||
}
|
||||
$result .= str_repeat($this->emptyBlock, $this->margin); // right margin
|
||||
$result .= "\n";
|
||||
}
|
||||
|
||||
// Bottom margin
|
||||
for ($x = 0; $x < $this->margin; $x++) {
|
||||
$result .= str_repeat($this->emptyBlock, $width + 2 * $this->margin)."\n";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* BaconQrCode
|
||||
*
|
||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
||||
*/
|
||||
|
||||
namespace BaconQrCode;
|
||||
|
||||
use BaconQrCode\Common\ErrorCorrectionLevel;
|
||||
use BaconQrCode\Encoder\Encoder;
|
||||
use BaconQrCode\Exception;
|
||||
use BaconQrCode\Renderer\RendererInterface;
|
||||
|
||||
/**
|
||||
* QR code writer.
|
||||
*/
|
||||
class Writer
|
||||
{
|
||||
/**
|
||||
* Renderer instance.
|
||||
*
|
||||
* @var RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Creates a new writer with a specific renderer.
|
||||
*
|
||||
* @param RendererInterface $renderer
|
||||
*/
|
||||
public function __construct(RendererInterface $renderer)
|
||||
{
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the renderer used to create a byte stream.
|
||||
*
|
||||
* @param RendererInterface $renderer
|
||||
* @return Writer
|
||||
*/
|
||||
public function setRenderer(RendererInterface $renderer)
|
||||
{
|
||||
$this->renderer = $renderer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the renderer used to create a byte stream.
|
||||
*
|
||||
* @return RendererInterface
|
||||
*/
|
||||
public function getRenderer()
|
||||
{
|
||||
return $this->renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes QR code and returns it as string.
|
||||
*
|
||||
* Content is a string which *should* be encoded in UTF-8, in case there are
|
||||
* non ASCII-characters present.
|
||||
*
|
||||
* @param string $content
|
||||
* @param string $encoding
|
||||
* @param integer $ecLevel
|
||||
* @return string
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function writeString(
|
||||
$content,
|
||||
$encoding = Encoder::DEFAULT_BYTE_MODE_ECODING,
|
||||
$ecLevel = ErrorCorrectionLevel::L
|
||||
) {
|
||||
if (strlen($content) === 0) {
|
||||
throw new Exception\InvalidArgumentException('Found empty contents');
|
||||
}
|
||||
|
||||
$qrCode = Encoder::encode($content, new ErrorCorrectionLevel($ecLevel), $encoding);
|
||||
|
||||
return $this->getRenderer()->render($qrCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes QR code to a file.
|
||||
*
|
||||
* @see Writer::writeString()
|
||||
* @param string $content
|
||||
* @param string $filename
|
||||
* @param string $encoding
|
||||
* @param integer $ecLevel
|
||||
* @return void
|
||||
*/
|
||||
public function writeFile(
|
||||
$content,
|
||||
$filename,
|
||||
$encoding = Encoder::DEFAULT_BYTE_MODE_ECODING,
|
||||
$ecLevel = ErrorCorrectionLevel::L
|
||||
) {
|
||||
file_put_contents($filename, $this->writeString($content, $encoding, $ecLevel));
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
../fguillot/picofeed/picofeed
|
||||
+445
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see http://www.php-fig.org/psr/psr-0/
|
||||
* @see http://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath.'\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
$length = $this->prefixLengthsPsr4[$first][$search];
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
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,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'ZendXml\\' => array($vendorDir . '/zendframework/zendxml/library'),
|
||||
'PicoFeed' => array($vendorDir . '/fguillot/picofeed/lib'),
|
||||
'BaconQrCode' => array($vendorDir . '/bacon/bacon-qr-code/src'),
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'),
|
||||
'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'),
|
||||
);
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInitf3438a4bfc092aad40a104edf0a3eb02
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInitf3438a4bfc092aad40a104edf0a3eb02', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitf3438a4bfc092aad40a104edf0a3eb02', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require_once __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitf3438a4bfc092aad40a104edf0a3eb02::getInitializer($loader));
|
||||
} else {
|
||||
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->set($namespace, $path);
|
||||
}
|
||||
|
||||
$map = require __DIR__ . '/autoload_psr4.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->setPsr4($namespace, $path);
|
||||
}
|
||||
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
}
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInitf3438a4bfc092aad40a104edf0a3eb02
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'R' =>
|
||||
array (
|
||||
'RobThree\\Auth\\' => 14,
|
||||
),
|
||||
'C' =>
|
||||
array (
|
||||
'Composer\\Semver\\' => 16,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'RobThree\\Auth\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/robthree/twofactorauth/lib',
|
||||
),
|
||||
'Composer\\Semver\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/composer/semver/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixesPsr0 = array (
|
||||
'Z' =>
|
||||
array (
|
||||
'ZendXml\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/zendframework/zendxml/library',
|
||||
),
|
||||
),
|
||||
'P' =>
|
||||
array (
|
||||
'PicoFeed' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/fguillot/picofeed/lib',
|
||||
),
|
||||
),
|
||||
'B' =>
|
||||
array (
|
||||
'BaconQrCode' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/bacon/bacon-qr-code/src',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInitf3438a4bfc092aad40a104edf0a3eb02::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInitf3438a4bfc092aad40a104edf0a3eb02::$prefixDirsPsr4;
|
||||
$loader->prefixesPsr0 = ComposerStaticInitf3438a4bfc092aad40a104edf0a3eb02::$prefixesPsr0;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
[
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "1.4.2",
|
||||
"version_normalized": "1.4.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "c7cb9a2095a074d131b65a8a0cd294479d785573"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573",
|
||||
"reference": "c7cb9a2095a074d131b65a8a0cd294479d785573",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3.2 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.5 || ^5.0.5",
|
||||
"phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
|
||||
},
|
||||
"time": "2016-08-30T16:08:34+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Composer\\Semver\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nils Adermann",
|
||||
"email": "naderman@naderman.de",
|
||||
"homepage": "http://www.naderman.de"
|
||||
},
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "http://seld.be"
|
||||
},
|
||||
{
|
||||
"name": "Rob Bast",
|
||||
"email": "rob.bast@gmail.com",
|
||||
"homepage": "http://robbast.nl"
|
||||
}
|
||||
],
|
||||
"description": "Semver library that offers utilities, version constraint parsing and validation.",
|
||||
"keywords": [
|
||||
"semantic",
|
||||
"semver",
|
||||
"validation",
|
||||
"versioning"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "zendframework/zendxml",
|
||||
"version": "1.0.2",
|
||||
"version_normalized": "1.0.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/zendframework/ZendXml.git",
|
||||
"reference": "7b64507bc35d841c9c5802d67f6f87ef8e1a58c9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/zendframework/ZendXml/zipball/7b64507bc35d841c9c5802d67f6f87ef8e1a58c9",
|
||||
"reference": "7b64507bc35d841c9c5802d67f6f87ef8e1a58c9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3.3 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^3.7 || ^4.0",
|
||||
"squizlabs/php_codesniffer": "^1.5"
|
||||
},
|
||||
"time": "2016-02-04T21:02:08+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"ZendXml\\": "library/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "Utility library for XML usage, best practices, and security in PHP",
|
||||
"homepage": "http://packages.zendframework.com/",
|
||||
"keywords": [
|
||||
"security",
|
||||
"xml",
|
||||
"zf2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fguillot/picofeed",
|
||||
"version": "v0.1.35",
|
||||
"version_normalized": "0.1.35.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/miniflux/picoFeed.git",
|
||||
"reference": "3a27b47de31eedec075c719f961783c5db7a7b08"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/miniflux/picoFeed/zipball/3a27b47de31eedec075c719f961783c5db7a7b08",
|
||||
"reference": "3a27b47de31eedec075c719f961783c5db7a7b08",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-xml": "*",
|
||||
"php": ">=5.3.0",
|
||||
"zendframework/zendxml": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpdocumentor/reflection-docblock": "2.0.4",
|
||||
"phpunit/phpunit": "4.8.26",
|
||||
"symfony/yaml": "2.8.7"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "PicoFeed will use cURL if present"
|
||||
},
|
||||
"time": "2017-06-20T22:54:47+00:00",
|
||||
"bin": [
|
||||
"picofeed"
|
||||
],
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"PicoFeed": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Frédéric Guillot"
|
||||
}
|
||||
],
|
||||
"description": "Modern library to handle RSS/Atom feeds",
|
||||
"homepage": "https://github.com/miniflux/picoFeed"
|
||||
},
|
||||
{
|
||||
"name": "robthree/twofactorauth",
|
||||
"version": "1.6",
|
||||
"version_normalized": "1.6.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/RobThree/TwoFactorAuth.git",
|
||||
"reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/5093ab230cd8f1296d792afb6a49545f37e7fd5a",
|
||||
"reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "@stable"
|
||||
},
|
||||
"time": "2017-02-17T15:24:54+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"RobThree\\Auth\\": "lib"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Rob Janssen",
|
||||
"homepage": "http://robiii.me",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Two Factor Authentication",
|
||||
"homepage": "https://github.com/RobThree/TwoFactorAuth",
|
||||
"keywords": [
|
||||
"Authentication",
|
||||
"MFA",
|
||||
"Multi Factor Authentication",
|
||||
"Two Factor Authentication",
|
||||
"authenticator",
|
||||
"authy",
|
||||
"php",
|
||||
"tfa"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
"version": "1.0.1",
|
||||
"version_normalized": "1.0.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Bacon/BaconQrCode.git",
|
||||
"reference": "031a2ce68c5794064b49d11775b2daf45c96e21c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/031a2ce68c5794064b49d11775b2daf45c96e21c",
|
||||
"reference": "031a2ce68c5794064b49d11775b2daf45c96e21c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gd": "to generate QR code images"
|
||||
},
|
||||
"time": "2016-01-09T22:55:35+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"BaconQrCode": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Scholzen 'DASPRiD'",
|
||||
"email": "mail@dasprids.de",
|
||||
"homepage": "http://www.dasprids.de",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "BaconQrCode is a QR code generator for PHP.",
|
||||
"homepage": "https://github.com/Bacon/BaconQrCode"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,66 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
### [1.4.2] 2016-08-30
|
||||
|
||||
* Fixed: collapsing of complex constraints lead to buggy constraints
|
||||
|
||||
### [1.4.1] 2016-06-02
|
||||
|
||||
* Changed: branch-like requirements no longer strip build metadata - [composer/semver#38](https://github.com/composer/semver/pull/38).
|
||||
|
||||
### [1.4.0] 2016-03-30
|
||||
|
||||
* Added: getters on MultiConstraint - [composer/semver#35](https://github.com/composer/semver/pull/35).
|
||||
|
||||
### [1.3.0] 2016-02-25
|
||||
|
||||
* Fixed: stability parsing - [composer/composer#1234](https://github.com/composer/composer/issues/4889).
|
||||
* Changed: collapse contiguous constraints when possible.
|
||||
|
||||
### [1.2.0] 2015-11-10
|
||||
|
||||
* Changed: allow multiple numerical identifiers in 'pre-release' version part.
|
||||
* Changed: add more 'v' prefix support.
|
||||
|
||||
### [1.1.0] 2015-11-03
|
||||
|
||||
* Changed: dropped redundant `test` namespace.
|
||||
* Changed: minor adjustment in datetime parsing normalization.
|
||||
* Changed: `ConstraintInterface` relaxed, setPrettyString is not required anymore.
|
||||
* Changed: `AbstractConstraint` marked deprecated, will be removed in 2.0.
|
||||
* Changed: `Constraint` is now extensible.
|
||||
|
||||
### [1.0.0] 2015-09-21
|
||||
|
||||
* Break: `VersionConstraint` renamed to `Constraint`.
|
||||
* Break: `SpecificConstraint` renamed to `AbstractConstraint`.
|
||||
* Break: `LinkConstraintInterface` renamed to `ConstraintInterface`.
|
||||
* Break: `VersionParser::parseNameVersionPairs` was removed.
|
||||
* Changed: `VersionParser::parseConstraints` allows (but ignores) build metadata now.
|
||||
* Changed: `VersionParser::parseConstraints` allows (but ignores) prefixing numeric versions with a 'v' now.
|
||||
* Changed: Fixed namespace(s) of test files.
|
||||
* Changed: `Comparator::compare` no longer throws `InvalidArgumentException`.
|
||||
* Changed: `Constraint` now throws `InvalidArgumentException`.
|
||||
|
||||
### [0.1.0] 2015-07-23
|
||||
|
||||
* Added: `Composer\Semver\Comparator`, various methods to compare versions.
|
||||
* Added: various documents such as README.md, LICENSE, etc.
|
||||
* Added: configuration files for Git, Travis, php-cs-fixer, phpunit.
|
||||
* Break: the following namespaces were renamed:
|
||||
- Namespace: `Composer\Package\Version` -> `Composer\Semver`
|
||||
- Namespace: `Composer\Package\LinkConstraint` -> `Composer\Semver\Constraint`
|
||||
- Namespace: `Composer\Test\Package\Version` -> `Composer\Test\Semver`
|
||||
- Namespace: `Composer\Test\Package\LinkConstraint` -> `Composer\Test\Semver\Constraint`
|
||||
* Changed: code style using php-cs-fixer.
|
||||
|
||||
[1.4.1]: https://github.com/composer/semver/compare/1.4.0...1.4.1
|
||||
[1.4.0]: https://github.com/composer/semver/compare/1.3.0...1.4.0
|
||||
[1.3.0]: https://github.com/composer/semver/compare/1.2.0...1.3.0
|
||||
[1.2.0]: https://github.com/composer/semver/compare/1.1.0...1.2.0
|
||||
[1.1.0]: https://github.com/composer/semver/compare/1.0.0...1.1.0
|
||||
[1.0.0]: https://github.com/composer/semver/compare/0.1.0...1.0.0
|
||||
[0.1.0]: https://github.com/composer/semver/compare/5e0b9a4da...0.1.0
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2015 Composer
|
||||
|
||||
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,70 @@
|
||||
composer/semver
|
||||
===============
|
||||
|
||||
Semver library that offers utilities, version constraint parsing and validation.
|
||||
|
||||
Originally written as part of [composer/composer](https://github.com/composer/composer),
|
||||
now extracted and made available as a stand-alone library.
|
||||
|
||||
[](https://travis-ci.org/composer/semver)
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install the latest version with:
|
||||
|
||||
```bash
|
||||
$ composer require composer/semver
|
||||
```
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* PHP 5.3.2 is required but using the latest version of PHP is highly recommended.
|
||||
|
||||
|
||||
Version Comparison
|
||||
------------------
|
||||
|
||||
For details on how versions are compared, refer to the [Versions](https://getcomposer.org/doc/articles/versions.md)
|
||||
article in the documentation section of the [getcomposer.org](https://getcomposer.org) website.
|
||||
|
||||
|
||||
Basic usage
|
||||
-----------
|
||||
|
||||
### Comparator
|
||||
|
||||
The `Composer\Semver\Comparator` class provides the following methods for comparing versions:
|
||||
|
||||
* greaterThan($v1, $v2)
|
||||
* greaterThanOrEqualTo($v1, $v2)
|
||||
* lessThan($v1, $v2)
|
||||
* lessThanOrEqualTo($v1, $v2)
|
||||
* equalTo($v1, $v2)
|
||||
* notEqualTo($v1, $v2)
|
||||
|
||||
Each function takes two version strings as arguments. For example:
|
||||
|
||||
```php
|
||||
use Composer\Semver\Comparator;
|
||||
|
||||
Comparator::greaterThan('1.25.0', '1.24.0'); // 1.25.0 > 1.24.0
|
||||
```
|
||||
|
||||
### Semver
|
||||
|
||||
The `Composer\Semver\Semver` class provides the following methods:
|
||||
|
||||
* satisfies($version, $constraints)
|
||||
* satisfiedBy(array $versions, $constraint)
|
||||
* sort($versions)
|
||||
* rsort($versions)
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
composer/semver is licensed under the MIT License, see the LICENSE file for details.
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"description": "Semver library that offers utilities, version constraint parsing and validation.",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"semver",
|
||||
"semantic",
|
||||
"versioning",
|
||||
"validation"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nils Adermann",
|
||||
"email": "naderman@naderman.de",
|
||||
"homepage": "http://www.naderman.de"
|
||||
},
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "http://seld.be"
|
||||
},
|
||||
{
|
||||
"name": "Rob Bast",
|
||||
"email": "rob.bast@gmail.com",
|
||||
"homepage": "http://robbast.nl"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"irc": "irc://irc.freenode.org/composer",
|
||||
"issues": "https://github.com/composer/semver/issues"
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3.2 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.5 || ^5.0.5",
|
||||
"phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Composer\\Semver\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Composer\\Semver\\": "tests"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of composer/semver.
|
||||
*
|
||||
* (c) Composer <https://github.com/composer>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Semver;
|
||||
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
|
||||
class Comparator
|
||||
{
|
||||
/**
|
||||
* Evaluates the expression: $version1 > $version2.
|
||||
*
|
||||
* @param string $version1
|
||||
* @param string $version2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function greaterThan($version1, $version2)
|
||||
{
|
||||
return self::compare($version1, '>', $version2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the expression: $version1 >= $version2.
|
||||
*
|
||||
* @param string $version1
|
||||
* @param string $version2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function greaterThanOrEqualTo($version1, $version2)
|
||||
{
|
||||
return self::compare($version1, '>=', $version2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the expression: $version1 < $version2.
|
||||
*
|
||||
* @param string $version1
|
||||
* @param string $version2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function lessThan($version1, $version2)
|
||||
{
|
||||
return self::compare($version1, '<', $version2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the expression: $version1 <= $version2.
|
||||
*
|
||||
* @param string $version1
|
||||
* @param string $version2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function lessThanOrEqualTo($version1, $version2)
|
||||
{
|
||||
return self::compare($version1, '<=', $version2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the expression: $version1 == $version2.
|
||||
*
|
||||
* @param string $version1
|
||||
* @param string $version2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function equalTo($version1, $version2)
|
||||
{
|
||||
return self::compare($version1, '==', $version2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the expression: $version1 != $version2.
|
||||
*
|
||||
* @param string $version1
|
||||
* @param string $version2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function notEqualTo($version1, $version2)
|
||||
{
|
||||
return self::compare($version1, '!=', $version2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the expression: $version1 $operator $version2.
|
||||
*
|
||||
* @param string $version1
|
||||
* @param string $operator
|
||||
* @param string $version2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function compare($version1, $operator, $version2)
|
||||
{
|
||||
$constraint = new Constraint($operator, $version2);
|
||||
|
||||
return $constraint->matches(new Constraint('==', $version1));
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of composer/semver.
|
||||
*
|
||||
* (c) Composer <https://github.com/composer>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Semver\Constraint;
|
||||
|
||||
trigger_error('The ' . __CLASS__ . ' abstract class is deprecated, there is no replacement for it, it will be removed in the next major version.', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Base constraint class.
|
||||
*/
|
||||
abstract class AbstractConstraint implements ConstraintInterface
|
||||
{
|
||||
/** @var string */
|
||||
protected $prettyString;
|
||||
|
||||
/**
|
||||
* @param ConstraintInterface $provider
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matches(ConstraintInterface $provider)
|
||||
{
|
||||
if ($provider instanceof $this) {
|
||||
// see note at bottom of this class declaration
|
||||
return $this->matchSpecific($provider);
|
||||
}
|
||||
|
||||
// turn matching around to find a match
|
||||
return $provider->matches($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prettyString
|
||||
*/
|
||||
public function setPrettyString($prettyString)
|
||||
{
|
||||
$this->prettyString = $prettyString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPrettyString()
|
||||
{
|
||||
if ($this->prettyString) {
|
||||
return $this->prettyString;
|
||||
}
|
||||
|
||||
return $this->__toString();
|
||||
}
|
||||
|
||||
// implementations must implement a method of this format:
|
||||
// not declared abstract here because type hinting violates parameter coherence (TODO right word?)
|
||||
// public function matchSpecific(<SpecificConstraintType> $provider);
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of composer/semver.
|
||||
*
|
||||
* (c) Composer <https://github.com/composer>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Semver\Constraint;
|
||||
|
||||
/**
|
||||
* Defines a constraint.
|
||||
*/
|
||||
class Constraint implements ConstraintInterface
|
||||
{
|
||||
/* operator integer values */
|
||||
const OP_EQ = 0;
|
||||
const OP_LT = 1;
|
||||
const OP_LE = 2;
|
||||
const OP_GT = 3;
|
||||
const OP_GE = 4;
|
||||
const OP_NE = 5;
|
||||
|
||||
/**
|
||||
* Operator to integer translation table.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $transOpStr = array(
|
||||
'=' => self::OP_EQ,
|
||||
'==' => self::OP_EQ,
|
||||
'<' => self::OP_LT,
|
||||
'<=' => self::OP_LE,
|
||||
'>' => self::OP_GT,
|
||||
'>=' => self::OP_GE,
|
||||
'<>' => self::OP_NE,
|
||||
'!=' => self::OP_NE,
|
||||
);
|
||||
|
||||
/**
|
||||
* Integer to operator translation table.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $transOpInt = array(
|
||||
self::OP_EQ => '==',
|
||||
self::OP_LT => '<',
|
||||
self::OP_LE => '<=',
|
||||
self::OP_GT => '>',
|
||||
self::OP_GE => '>=',
|
||||
self::OP_NE => '!=',
|
||||
);
|
||||
|
||||
/** @var string */
|
||||
protected $operator;
|
||||
|
||||
/** @var string */
|
||||
protected $version;
|
||||
|
||||
/** @var string */
|
||||
protected $prettyString;
|
||||
|
||||
/**
|
||||
* @param ConstraintInterface $provider
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matches(ConstraintInterface $provider)
|
||||
{
|
||||
if ($provider instanceof $this) {
|
||||
return $this->matchSpecific($provider);
|
||||
}
|
||||
|
||||
// turn matching around to find a match
|
||||
return $provider->matches($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prettyString
|
||||
*/
|
||||
public function setPrettyString($prettyString)
|
||||
{
|
||||
$this->prettyString = $prettyString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPrettyString()
|
||||
{
|
||||
if ($this->prettyString) {
|
||||
return $this->prettyString;
|
||||
}
|
||||
|
||||
return $this->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all supported comparison operators.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getSupportedOperators()
|
||||
{
|
||||
return array_keys(self::$transOpStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets operator and version to compare with.
|
||||
*
|
||||
* @param string $operator
|
||||
* @param string $version
|
||||
*
|
||||
* @throws \InvalidArgumentException if invalid operator is given.
|
||||
*/
|
||||
public function __construct($operator, $version)
|
||||
{
|
||||
if (!isset(self::$transOpStr[$operator])) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Invalid operator "%s" given, expected one of: %s',
|
||||
$operator,
|
||||
implode(', ', self::getSupportedOperators())
|
||||
));
|
||||
}
|
||||
|
||||
$this->operator = self::$transOpStr[$operator];
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $a
|
||||
* @param string $b
|
||||
* @param string $operator
|
||||
* @param bool $compareBranches
|
||||
*
|
||||
* @throws \InvalidArgumentException if invalid operator is given.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function versionCompare($a, $b, $operator, $compareBranches = false)
|
||||
{
|
||||
if (!isset(self::$transOpStr[$operator])) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Invalid operator "%s" given, expected one of: %s',
|
||||
$operator,
|
||||
implode(', ', self::getSupportedOperators())
|
||||
));
|
||||
}
|
||||
|
||||
$aIsBranch = 'dev-' === substr($a, 0, 4);
|
||||
$bIsBranch = 'dev-' === substr($b, 0, 4);
|
||||
|
||||
if ($aIsBranch && $bIsBranch) {
|
||||
return $operator === '==' && $a === $b;
|
||||
}
|
||||
|
||||
// when branches are not comparable, we make sure dev branches never match anything
|
||||
if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return version_compare($a, $b, $operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Constraint $provider
|
||||
* @param bool $compareBranches
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matchSpecific(Constraint $provider, $compareBranches = false)
|
||||
{
|
||||
$noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]);
|
||||
$providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]);
|
||||
|
||||
$isEqualOp = self::OP_EQ === $this->operator;
|
||||
$isNonEqualOp = self::OP_NE === $this->operator;
|
||||
$isProviderEqualOp = self::OP_EQ === $provider->operator;
|
||||
$isProviderNonEqualOp = self::OP_NE === $provider->operator;
|
||||
|
||||
// '!=' operator is match when other operator is not '==' operator or version is not match
|
||||
// these kinds of comparisons always have a solution
|
||||
if ($isNonEqualOp || $isProviderNonEqualOp) {
|
||||
return !$isEqualOp && !$isProviderEqualOp
|
||||
|| $this->versionCompare($provider->version, $this->version, '!=', $compareBranches);
|
||||
}
|
||||
|
||||
// an example for the condition is <= 2.0 & < 1.0
|
||||
// these kinds of comparisons always have a solution
|
||||
if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->versionCompare($provider->version, $this->version, self::$transOpInt[$this->operator], $compareBranches)) {
|
||||
// special case, e.g. require >= 1.0 and provide < 1.0
|
||||
// 1.0 >= 1.0 but 1.0 is outside of the provided interval
|
||||
if ($provider->version === $this->version
|
||||
&& self::$transOpInt[$provider->operator] === $providerNoEqualOp
|
||||
&& self::$transOpInt[$this->operator] !== $noEqualOp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return self::$transOpInt[$this->operator] . ' ' . $this->version;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of composer/semver.
|
||||
*
|
||||
* (c) Composer <https://github.com/composer>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Semver\Constraint;
|
||||
|
||||
interface ConstraintInterface
|
||||
{
|
||||
/**
|
||||
* @param ConstraintInterface $provider
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matches(ConstraintInterface $provider);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPrettyString();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of composer/semver.
|
||||
*
|
||||
* (c) Composer <https://github.com/composer>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Semver\Constraint;
|
||||
|
||||
/**
|
||||
* Defines the absence of a constraint.
|
||||
*/
|
||||
class EmptyConstraint implements ConstraintInterface
|
||||
{
|
||||
/** @var string */
|
||||
protected $prettyString;
|
||||
|
||||
/**
|
||||
* @param ConstraintInterface $provider
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matches(ConstraintInterface $provider)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $prettyString
|
||||
*/
|
||||
public function setPrettyString($prettyString)
|
||||
{
|
||||
$this->prettyString = $prettyString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPrettyString()
|
||||
{
|
||||
if ($this->prettyString) {
|
||||
return $this->prettyString;
|
||||
}
|
||||
|
||||
return $this->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return '[]';
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of composer/semver.
|
||||
*
|
||||
* (c) Composer <https://github.com/composer>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Semver\Constraint;
|
||||
|
||||
/**
|
||||
* Defines a conjunctive or disjunctive set of constraints.
|
||||
*/
|
||||
class MultiConstraint implements ConstraintInterface
|
||||
{
|
||||
/** @var ConstraintInterface[] */
|
||||
protected $constraints;
|
||||
|
||||
/** @var string */
|
||||
protected $prettyString;
|
||||
|
||||
/** @var bool */
|
||||
protected $conjunctive;
|
||||
|
||||
/**
|
||||
* @param ConstraintInterface[] $constraints A set of constraints
|
||||
* @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive
|
||||
*/
|
||||
public function __construct(array $constraints, $conjunctive = true)
|
||||
{
|
||||
$this->constraints = $constraints;
|
||||
$this->conjunctive = $conjunctive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ConstraintInterface[]
|
||||
*/
|
||||
public function getConstraints()
|
||||
{
|
||||
return $this->constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isConjunctive()
|
||||
{
|
||||
return $this->conjunctive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDisjunctive()
|
||||
{
|
||||
return !$this->conjunctive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConstraintInterface $provider
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matches(ConstraintInterface $provider)
|
||||
{
|
||||
if (false === $this->conjunctive) {
|
||||
foreach ($this->constraints as $constraint) {
|
||||
if ($constraint->matches($provider)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->constraints as $constraint) {
|
||||
if (!$constraint->matches($provider)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prettyString
|
||||
*/
|
||||
public function setPrettyString($prettyString)
|
||||
{
|
||||
$this->prettyString = $prettyString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPrettyString()
|
||||
{
|
||||
if ($this->prettyString) {
|
||||
return $this->prettyString;
|
||||
}
|
||||
|
||||
return $this->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$constraints = array();
|
||||
foreach ($this->constraints as $constraint) {
|
||||
$constraints[] = (string) $constraint;
|
||||
}
|
||||
|
||||
return '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of composer/semver.
|
||||
*
|
||||
* (c) Composer <https://github.com/composer>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Semver;
|
||||
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
|
||||
class Semver
|
||||
{
|
||||
const SORT_ASC = 1;
|
||||
const SORT_DESC = -1;
|
||||
|
||||
/** @var VersionParser */
|
||||
private static $versionParser;
|
||||
|
||||
/**
|
||||
* Determine if given version satisfies given constraints.
|
||||
*
|
||||
* @param string $version
|
||||
* @param string $constraints
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function satisfies($version, $constraints)
|
||||
{
|
||||
if (null === self::$versionParser) {
|
||||
self::$versionParser = new VersionParser();
|
||||
}
|
||||
|
||||
$versionParser = self::$versionParser;
|
||||
$provider = new Constraint('==', $versionParser->normalize($version));
|
||||
$constraints = $versionParser->parseConstraints($constraints);
|
||||
|
||||
return $constraints->matches($provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all versions that satisfy given constraints.
|
||||
*
|
||||
* @param array $versions
|
||||
* @param string $constraints
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function satisfiedBy(array $versions, $constraints)
|
||||
{
|
||||
$versions = array_filter($versions, function ($version) use ($constraints) {
|
||||
return Semver::satisfies($version, $constraints);
|
||||
});
|
||||
|
||||
return array_values($versions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort given array of versions.
|
||||
*
|
||||
* @param array $versions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function sort(array $versions)
|
||||
{
|
||||
return self::usort($versions, self::SORT_ASC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort given array of versions in reverse.
|
||||
*
|
||||
* @param array $versions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function rsort(array $versions)
|
||||
{
|
||||
return self::usort($versions, self::SORT_DESC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $versions
|
||||
* @param int $direction
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function usort(array $versions, $direction)
|
||||
{
|
||||
if (null === self::$versionParser) {
|
||||
self::$versionParser = new VersionParser();
|
||||
}
|
||||
|
||||
$versionParser = self::$versionParser;
|
||||
$normalized = array();
|
||||
|
||||
// Normalize outside of usort() scope for minor performance increase.
|
||||
// Creates an array of arrays: [[normalized, key], ...]
|
||||
foreach ($versions as $key => $version) {
|
||||
$normalized[] = array($versionParser->normalize($version), $key);
|
||||
}
|
||||
|
||||
usort($normalized, function (array $left, array $right) use ($direction) {
|
||||
if ($left[0] === $right[0]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Comparator::lessThan($left[0], $right[0])) {
|
||||
return -$direction;
|
||||
}
|
||||
|
||||
return $direction;
|
||||
});
|
||||
|
||||
// Recreate input array, using the original indexes which are now in sorted order.
|
||||
$sorted = array();
|
||||
foreach ($normalized as $item) {
|
||||
$sorted[] = $versions[$item[1]];
|
||||
}
|
||||
|
||||
return $sorted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,548 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of composer/semver.
|
||||
*
|
||||
* (c) Composer <https://github.com/composer>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Semver;
|
||||
|
||||
use Composer\Semver\Constraint\ConstraintInterface;
|
||||
use Composer\Semver\Constraint\EmptyConstraint;
|
||||
use Composer\Semver\Constraint\MultiConstraint;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
|
||||
/**
|
||||
* Version parser.
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class VersionParser
|
||||
{
|
||||
/**
|
||||
* Regex to match pre-release data (sort of).
|
||||
*
|
||||
* Due to backwards compatibility:
|
||||
* - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted.
|
||||
* - Only stabilities as recognized by Composer are allowed to precede a numerical identifier.
|
||||
* - Numerical-only pre-release identifiers are not supported, see tests.
|
||||
*
|
||||
* |--------------|
|
||||
* [major].[minor].[patch] -[pre-release] +[build-metadata]
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?';
|
||||
|
||||
/** @var array */
|
||||
private static $stabilities = array('stable', 'RC', 'beta', 'alpha', 'dev');
|
||||
|
||||
/**
|
||||
* Returns the stability of a version.
|
||||
*
|
||||
* @param string $version
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function parseStability($version)
|
||||
{
|
||||
$version = preg_replace('{#.+$}i', '', $version);
|
||||
|
||||
if ('dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4)) {
|
||||
return 'dev';
|
||||
}
|
||||
|
||||
preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match);
|
||||
if (!empty($match[3])) {
|
||||
return 'dev';
|
||||
}
|
||||
|
||||
if (!empty($match[1])) {
|
||||
if ('beta' === $match[1] || 'b' === $match[1]) {
|
||||
return 'beta';
|
||||
}
|
||||
if ('alpha' === $match[1] || 'a' === $match[1]) {
|
||||
return 'alpha';
|
||||
}
|
||||
if ('rc' === $match[1]) {
|
||||
return 'RC';
|
||||
}
|
||||
}
|
||||
|
||||
return 'stable';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stability
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function normalizeStability($stability)
|
||||
{
|
||||
$stability = strtolower($stability);
|
||||
|
||||
return $stability === 'rc' ? 'RC' : $stability;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a version string to be able to perform comparisons on it.
|
||||
*
|
||||
* @param string $version
|
||||
* @param string $fullVersion optional complete version string to give more context
|
||||
*
|
||||
* @throws \UnexpectedValueException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function normalize($version, $fullVersion = null)
|
||||
{
|
||||
$version = trim($version);
|
||||
if (null === $fullVersion) {
|
||||
$fullVersion = $version;
|
||||
}
|
||||
|
||||
// strip off aliasing
|
||||
if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
|
||||
$version = $match[1];
|
||||
}
|
||||
|
||||
// match master-like branches
|
||||
if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
|
||||
return '9999999-dev';
|
||||
}
|
||||
|
||||
// if requirement is branch-like, use full name
|
||||
if ('dev-' === strtolower(substr($version, 0, 4))) {
|
||||
return 'dev-' . substr($version, 4);
|
||||
}
|
||||
|
||||
// strip off build metadata
|
||||
if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) {
|
||||
$version = $match[1];
|
||||
}
|
||||
|
||||
// match classical versioning
|
||||
if (preg_match('{^v?(\d{1,5})(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) {
|
||||
$version = $matches[1]
|
||||
. (!empty($matches[2]) ? $matches[2] : '.0')
|
||||
. (!empty($matches[3]) ? $matches[3] : '.0')
|
||||
. (!empty($matches[4]) ? $matches[4] : '.0');
|
||||
$index = 5;
|
||||
// match date(time) based versioning
|
||||
} elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) {
|
||||
$version = preg_replace('{\D}', '.', $matches[1]);
|
||||
$index = 2;
|
||||
}
|
||||
|
||||
// add version modifiers if a version was matched
|
||||
if (isset($index)) {
|
||||
if (!empty($matches[$index])) {
|
||||
if ('stable' === $matches[$index]) {
|
||||
return $version;
|
||||
}
|
||||
$version .= '-' . $this->expandStability($matches[$index]) . (!empty($matches[$index + 1]) ? ltrim($matches[$index + 1], '.-') : '');
|
||||
}
|
||||
|
||||
if (!empty($matches[$index + 2])) {
|
||||
$version .= '-dev';
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
// match dev branches
|
||||
if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) {
|
||||
try {
|
||||
return $this->normalizeBranch($match[1]);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
$extraMessage = '';
|
||||
if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) {
|
||||
$extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
|
||||
} elseif (preg_match('{^' . preg_quote($version) . ' +as +}', $fullVersion)) {
|
||||
$extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison.
|
||||
*
|
||||
* @param string $branch Branch name (e.g. 2.1.x-dev)
|
||||
*
|
||||
* @return string|false Numeric prefix if present (e.g. 2.1.) or false
|
||||
*/
|
||||
public function parseNumericAliasPrefix($branch)
|
||||
{
|
||||
if (preg_match('{^(?P<version>(\d++\\.)*\d++)(?:\.x)?-dev$}i', $branch, $matches)) {
|
||||
return $matches['version'] . '.';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a branch name to be able to perform comparisons on it.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeBranch($name)
|
||||
{
|
||||
$name = trim($name);
|
||||
|
||||
if (in_array($name, array('master', 'trunk', 'default'))) {
|
||||
return $this->normalize($name);
|
||||
}
|
||||
|
||||
if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) {
|
||||
$version = '';
|
||||
for ($i = 1; $i < 5; ++$i) {
|
||||
$version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x';
|
||||
}
|
||||
|
||||
return str_replace('x', '9999999', $version) . '-dev';
|
||||
}
|
||||
|
||||
return 'dev-' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a constraint string into MultiConstraint and/or Constraint objects.
|
||||
*
|
||||
* @param string $constraints
|
||||
*
|
||||
* @return ConstraintInterface
|
||||
*/
|
||||
public function parseConstraints($constraints)
|
||||
{
|
||||
$prettyConstraint = $constraints;
|
||||
|
||||
if (preg_match('{^([^,\s]*?)@(' . implode('|', self::$stabilities) . ')$}i', $constraints, $match)) {
|
||||
$constraints = empty($match[1]) ? '*' : $match[1];
|
||||
}
|
||||
|
||||
if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraints, $match)) {
|
||||
$constraints = $match[1];
|
||||
}
|
||||
|
||||
$orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints));
|
||||
$orGroups = array();
|
||||
foreach ($orConstraints as $constraints) {
|
||||
$andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $constraints);
|
||||
if (count($andConstraints) > 1) {
|
||||
$constraintObjects = array();
|
||||
foreach ($andConstraints as $constraint) {
|
||||
foreach ($this->parseConstraint($constraint) as $parsedConstraint) {
|
||||
$constraintObjects[] = $parsedConstraint;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$constraintObjects = $this->parseConstraint($andConstraints[0]);
|
||||
}
|
||||
|
||||
if (1 === count($constraintObjects)) {
|
||||
$constraint = $constraintObjects[0];
|
||||
} else {
|
||||
$constraint = new MultiConstraint($constraintObjects);
|
||||
}
|
||||
|
||||
$orGroups[] = $constraint;
|
||||
}
|
||||
|
||||
if (1 === count($orGroups)) {
|
||||
$constraint = $orGroups[0];
|
||||
} elseif (2 === count($orGroups)
|
||||
// parse the two OR groups and if they are contiguous we collapse
|
||||
// them into one constraint
|
||||
&& $orGroups[0] instanceof MultiConstraint
|
||||
&& $orGroups[1] instanceof MultiConstraint
|
||||
&& 2 === count($orGroups[0]->getConstraints())
|
||||
&& 2 === count($orGroups[1]->getConstraints())
|
||||
&& ($a = (string) $orGroups[0])
|
||||
&& substr($a, 0, 3) === '[>=' && (false !== ($posA = strpos($a, '<', 4)))
|
||||
&& ($b = (string) $orGroups[1])
|
||||
&& substr($b, 0, 3) === '[>=' && (false !== ($posB = strpos($b, '<', 4)))
|
||||
&& substr($a, $posA + 2, -1) === substr($b, 4, $posB - 5)
|
||||
) {
|
||||
$constraint = new MultiConstraint(array(
|
||||
new Constraint('>=', substr($a, 4, $posA - 5)),
|
||||
new Constraint('<', substr($b, $posB + 2, -1)),
|
||||
));
|
||||
} else {
|
||||
$constraint = new MultiConstraint($orGroups, false);
|
||||
}
|
||||
|
||||
$constraint->setPrettyString($prettyConstraint);
|
||||
|
||||
return $constraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $constraint
|
||||
*
|
||||
* @throws \UnexpectedValueException
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseConstraint($constraint)
|
||||
{
|
||||
if (preg_match('{^([^,\s]+?)@(' . implode('|', self::$stabilities) . ')$}i', $constraint, $match)) {
|
||||
$constraint = $match[1];
|
||||
if ($match[2] !== 'stable') {
|
||||
$stabilityModifier = $match[2];
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match('{^v?[xX*](\.[xX*])*$}i', $constraint)) {
|
||||
return array(new EmptyConstraint());
|
||||
}
|
||||
|
||||
$versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?' . self::$modifierRegex . '(?:\+[^\s]+)?';
|
||||
|
||||
// Tilde Range
|
||||
//
|
||||
// Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
|
||||
// version, to ensure that unstable instances of the current version are allowed. However, if a stability
|
||||
// suffix is added to the constraint, then a >= match on the current version is used instead.
|
||||
if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) {
|
||||
if (substr($constraint, 0, 2) === '~>') {
|
||||
throw new \UnexpectedValueException(
|
||||
'Could not parse version constraint ' . $constraint . ': ' .
|
||||
'Invalid operator "~>", you probably meant to use the "~" operator'
|
||||
);
|
||||
}
|
||||
|
||||
// Work out which position in the version we are operating at
|
||||
if (isset($matches[4]) && '' !== $matches[4]) {
|
||||
$position = 4;
|
||||
} elseif (isset($matches[3]) && '' !== $matches[3]) {
|
||||
$position = 3;
|
||||
} elseif (isset($matches[2]) && '' !== $matches[2]) {
|
||||
$position = 2;
|
||||
} else {
|
||||
$position = 1;
|
||||
}
|
||||
|
||||
// Calculate the stability suffix
|
||||
$stabilitySuffix = '';
|
||||
if (!empty($matches[5])) {
|
||||
$stabilitySuffix .= '-' . $this->expandStability($matches[5]) . (!empty($matches[6]) ? $matches[6] : '');
|
||||
}
|
||||
|
||||
if (!empty($matches[7])) {
|
||||
$stabilitySuffix .= '-dev';
|
||||
}
|
||||
|
||||
if (!$stabilitySuffix) {
|
||||
$stabilitySuffix = '-dev';
|
||||
}
|
||||
|
||||
$lowVersion = $this->manipulateVersionString($matches, $position, 0) . $stabilitySuffix;
|
||||
$lowerBound = new Constraint('>=', $lowVersion);
|
||||
|
||||
// For upper bound, we increment the position of one more significance,
|
||||
// but highPosition = 0 would be illegal
|
||||
$highPosition = max(1, $position - 1);
|
||||
$highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev';
|
||||
$upperBound = new Constraint('<', $highVersion);
|
||||
|
||||
return array(
|
||||
$lowerBound,
|
||||
$upperBound,
|
||||
);
|
||||
}
|
||||
|
||||
// Caret Range
|
||||
//
|
||||
// Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple.
|
||||
// In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for
|
||||
// versions 0.X >=0.1.0, and no updates for versions 0.0.X
|
||||
if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) {
|
||||
// Work out which position in the version we are operating at
|
||||
if ('0' !== $matches[1] || '' === $matches[2]) {
|
||||
$position = 1;
|
||||
} elseif ('0' !== $matches[2] || '' === $matches[3]) {
|
||||
$position = 2;
|
||||
} else {
|
||||
$position = 3;
|
||||
}
|
||||
|
||||
// Calculate the stability suffix
|
||||
$stabilitySuffix = '';
|
||||
if (empty($matches[5]) && empty($matches[7])) {
|
||||
$stabilitySuffix .= '-dev';
|
||||
}
|
||||
|
||||
$lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
|
||||
$lowerBound = new Constraint('>=', $lowVersion);
|
||||
|
||||
// For upper bound, we increment the position of one more significance,
|
||||
// but highPosition = 0 would be illegal
|
||||
$highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
|
||||
$upperBound = new Constraint('<', $highVersion);
|
||||
|
||||
return array(
|
||||
$lowerBound,
|
||||
$upperBound,
|
||||
);
|
||||
}
|
||||
|
||||
// X Range
|
||||
//
|
||||
// Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple.
|
||||
// A partial version range is treated as an X-Range, so the special character is in fact optional.
|
||||
if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) {
|
||||
if (isset($matches[3]) && '' !== $matches[3]) {
|
||||
$position = 3;
|
||||
} elseif (isset($matches[2]) && '' !== $matches[2]) {
|
||||
$position = 2;
|
||||
} else {
|
||||
$position = 1;
|
||||
}
|
||||
|
||||
$lowVersion = $this->manipulateVersionString($matches, $position) . '-dev';
|
||||
$highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
|
||||
|
||||
if ($lowVersion === '0.0.0.0-dev') {
|
||||
return array(new Constraint('<', $highVersion));
|
||||
}
|
||||
|
||||
return array(
|
||||
new Constraint('>=', $lowVersion),
|
||||
new Constraint('<', $highVersion),
|
||||
);
|
||||
}
|
||||
|
||||
// Hyphen Range
|
||||
//
|
||||
// Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range,
|
||||
// then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in
|
||||
// the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but
|
||||
// nothing that would be greater than the provided tuple parts.
|
||||
if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) {
|
||||
// Calculate the stability suffix
|
||||
$lowStabilitySuffix = '';
|
||||
if (empty($matches[6]) && empty($matches[8])) {
|
||||
$lowStabilitySuffix = '-dev';
|
||||
}
|
||||
|
||||
$lowVersion = $this->normalize($matches['from']);
|
||||
$lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix);
|
||||
|
||||
$empty = function ($x) {
|
||||
return ($x === 0 || $x === '0') ? false : empty($x);
|
||||
};
|
||||
|
||||
if ((!$empty($matches[11]) && !$empty($matches[12])) || !empty($matches[14]) || !empty($matches[16])) {
|
||||
$highVersion = $this->normalize($matches['to']);
|
||||
$upperBound = new Constraint('<=', $highVersion);
|
||||
} else {
|
||||
$highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]);
|
||||
$highVersion = $this->manipulateVersionString($highMatch, $empty($matches[11]) ? 1 : 2, 1) . '-dev';
|
||||
$upperBound = new Constraint('<', $highVersion);
|
||||
}
|
||||
|
||||
return array(
|
||||
$lowerBound,
|
||||
$upperBound,
|
||||
);
|
||||
}
|
||||
|
||||
// Basic Comparators
|
||||
if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
|
||||
try {
|
||||
$version = $this->normalize($matches[2]);
|
||||
|
||||
if (!empty($stabilityModifier) && $this->parseStability($version) === 'stable') {
|
||||
$version .= '-' . $stabilityModifier;
|
||||
} elseif ('<' === $matches[1] || '>=' === $matches[1]) {
|
||||
if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
|
||||
if (substr($matches[2], 0, 4) !== 'dev-') {
|
||||
$version .= '-dev';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array(new Constraint($matches[1] ?: '=', $version));
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
$message = 'Could not parse version constraint ' . $constraint;
|
||||
if (isset($e)) {
|
||||
$message .= ': ' . $e->getMessage();
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment, decrement, or simply pad a version number.
|
||||
*
|
||||
* Support function for {@link parseConstraint()}
|
||||
*
|
||||
* @param array $matches Array with version parts in array indexes 1,2,3,4
|
||||
* @param int $position 1,2,3,4 - which segment of the version to increment/decrement
|
||||
* @param int $increment
|
||||
* @param string $pad The string to pad version parts after $position
|
||||
*
|
||||
* @return string The new version
|
||||
*/
|
||||
private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0')
|
||||
{
|
||||
for ($i = 4; $i > 0; --$i) {
|
||||
if ($i > $position) {
|
||||
$matches[$i] = $pad;
|
||||
} elseif ($i === $position && $increment) {
|
||||
$matches[$i] += $increment;
|
||||
// If $matches[$i] was 0, carry the decrement
|
||||
if ($matches[$i] < 0) {
|
||||
$matches[$i] = $pad;
|
||||
--$position;
|
||||
|
||||
// Return null on a carry overflow
|
||||
if ($i === 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4];
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand shorthand stability string to long version.
|
||||
*
|
||||
* @param string $stability
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function expandStability($stability)
|
||||
{
|
||||
$stability = strtolower($stability);
|
||||
|
||||
switch ($stability) {
|
||||
case 'a':
|
||||
return 'alpha';
|
||||
case 'b':
|
||||
return 'beta';
|
||||
case 'p':
|
||||
case 'pl':
|
||||
return 'patch';
|
||||
case 'rc':
|
||||
return 'RC';
|
||||
default:
|
||||
return $stability;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Frederic Guillot
|
||||
|
||||
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,38 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed;
|
||||
|
||||
use PicoFeed\Config\Config;
|
||||
use PicoFeed\Logging\Logger;
|
||||
|
||||
/**
|
||||
* Base class
|
||||
*
|
||||
* @package PicoFeed
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class Base
|
||||
{
|
||||
/**
|
||||
* Config class instance
|
||||
*
|
||||
* @access protected
|
||||
* @var \PicoFeed\Config\Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \PicoFeed\Config\Config $config Config class instance
|
||||
*/
|
||||
public function __construct(Config $config = null)
|
||||
{
|
||||
$this->config = $config ?: new Config();
|
||||
Logger::setTimezone($this->config->getTimezone());
|
||||
}
|
||||
|
||||
public function setConfig(Config $config) {
|
||||
$this->config = $config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,712 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use LogicException;
|
||||
use PicoFeed\Logging\Logger;
|
||||
use PicoFeed\Config\Config;
|
||||
|
||||
/**
|
||||
* Client class.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class Client
|
||||
{
|
||||
/**
|
||||
* Flag that say if the resource have been modified.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $is_modified = true;
|
||||
|
||||
/**
|
||||
* HTTP Content-Type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $content_type = '';
|
||||
|
||||
/**
|
||||
* HTTP encoding.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $encoding = '';
|
||||
|
||||
/**
|
||||
* HTTP request headers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $request_headers = array();
|
||||
|
||||
/**
|
||||
* HTTP Etag header.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $etag = '';
|
||||
|
||||
/**
|
||||
* HTTP Last-Modified header.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $last_modified = '';
|
||||
|
||||
/**
|
||||
* Expiration DateTime
|
||||
*
|
||||
* @var DateTime
|
||||
*/
|
||||
protected $expiration = null;
|
||||
|
||||
/**
|
||||
* Proxy hostname.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $proxy_hostname = '';
|
||||
|
||||
/**
|
||||
* Proxy port.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $proxy_port = 3128;
|
||||
|
||||
/**
|
||||
* Proxy username.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $proxy_username = '';
|
||||
|
||||
/**
|
||||
* Proxy password.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $proxy_password = '';
|
||||
|
||||
/**
|
||||
* Basic auth username.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $username = '';
|
||||
|
||||
/**
|
||||
* Basic auth password.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $password = '';
|
||||
|
||||
/**
|
||||
* CURL options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $additional_curl_options = array();
|
||||
|
||||
/**
|
||||
* Client connection timeout.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $timeout = 10;
|
||||
|
||||
/**
|
||||
* User-agent.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $user_agent = 'PicoFeed (https://github.com/miniflux/picoFeed)';
|
||||
|
||||
/**
|
||||
* Real URL used (can be changed after a HTTP redirect).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url = '';
|
||||
|
||||
/**
|
||||
* Page/Feed content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content = '';
|
||||
|
||||
/**
|
||||
* Number maximum of HTTP redirections to avoid infinite loops.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $max_redirects = 5;
|
||||
|
||||
/**
|
||||
* Maximum size of the HTTP body response.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $max_body_size = 2097152; // 2MB
|
||||
|
||||
/**
|
||||
* HTTP response status code.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $status_code = 0;
|
||||
|
||||
/**
|
||||
* Enables direct passthrough to requesting client.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $passthrough = false;
|
||||
|
||||
/**
|
||||
* Do the HTTP request.
|
||||
*
|
||||
* @abstract
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function doRequest();
|
||||
|
||||
/**
|
||||
* Get client instance: curl or stream driver.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @return \PicoFeed\Client\Client
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (function_exists('curl_init')) {
|
||||
return new Curl();
|
||||
} elseif (ini_get('allow_url_fopen')) {
|
||||
return new Stream();
|
||||
}
|
||||
|
||||
throw new LogicException('You must have "allow_url_fopen=1" or curl extension installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add HTTP Header to the request.
|
||||
*
|
||||
* @param array $headers
|
||||
*/
|
||||
public function setHeaders($headers)
|
||||
{
|
||||
$this->request_headers = $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the HTTP request.
|
||||
*
|
||||
* @param string $url URL
|
||||
*
|
||||
* @return Client
|
||||
*/
|
||||
public function execute($url = '')
|
||||
{
|
||||
if ($url !== '') {
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
Logger::setMessage(get_called_class().' Fetch URL: '.$this->url);
|
||||
Logger::setMessage(get_called_class().' Etag provided: '.$this->etag);
|
||||
Logger::setMessage(get_called_class().' Last-Modified provided: '.$this->last_modified);
|
||||
|
||||
$response = $this->doRequest();
|
||||
|
||||
$this->status_code = $response['status'];
|
||||
$this->handleNotModifiedResponse($response);
|
||||
$this->handleErrorResponse($response);
|
||||
$this->handleNormalResponse($response);
|
||||
|
||||
$this->expiration = $this->parseExpiration($response['headers']);
|
||||
Logger::setMessage(get_called_class().' Expiration: '.$this->expiration->format(DATE_ISO8601));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle not modified response.
|
||||
*
|
||||
* @param array $response Client response
|
||||
*/
|
||||
protected function handleNotModifiedResponse(array $response)
|
||||
{
|
||||
if ($response['status'] == 304) {
|
||||
$this->is_modified = false;
|
||||
} elseif ($response['status'] == 200) {
|
||||
$this->is_modified = $this->hasBeenModified($response, $this->etag, $this->last_modified);
|
||||
$this->etag = $this->getHeader($response, 'ETag');
|
||||
$this->last_modified = $this->getHeader($response, 'Last-Modified');
|
||||
}
|
||||
|
||||
if ($this->is_modified === false) {
|
||||
Logger::setMessage(get_called_class().' Resource not modified');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Http Error codes
|
||||
*
|
||||
* @param array $response Client response
|
||||
* @throws ForbiddenException
|
||||
* @throws InvalidUrlException
|
||||
* @throws UnauthorizedException
|
||||
*/
|
||||
protected function handleErrorResponse(array $response)
|
||||
{
|
||||
$status = $response['status'];
|
||||
if ($status == 401) {
|
||||
throw new UnauthorizedException('Wrong or missing credentials');
|
||||
} else if ($status == 403) {
|
||||
throw new ForbiddenException('Not allowed to access resource');
|
||||
} else if ($status == 404) {
|
||||
throw new InvalidUrlException('Resource not found');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle normal response.
|
||||
*
|
||||
* @param array $response Client response
|
||||
*/
|
||||
protected function handleNormalResponse(array $response)
|
||||
{
|
||||
if ($response['status'] == 200) {
|
||||
$this->content = $response['body'];
|
||||
$this->content_type = $this->findContentType($response);
|
||||
$this->encoding = $this->findCharset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a request has been modified according to the parameters.
|
||||
*
|
||||
* @param array $response
|
||||
* @param string $etag
|
||||
* @param string $lastModified
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function hasBeenModified($response, $etag, $lastModified)
|
||||
{
|
||||
$headers = array(
|
||||
'Etag' => $etag,
|
||||
'Last-Modified' => $lastModified,
|
||||
);
|
||||
|
||||
// Compare the values for each header that is present
|
||||
$presentCacheHeaderCount = 0;
|
||||
foreach ($headers as $key => $value) {
|
||||
if (isset($response['headers'][$key])) {
|
||||
if ($response['headers'][$key] !== $value) {
|
||||
return true;
|
||||
}
|
||||
++$presentCacheHeaderCount;
|
||||
}
|
||||
}
|
||||
|
||||
// If at least one header is present and the values match, the response
|
||||
// was not modified
|
||||
if ($presentCacheHeaderCount > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find content type from response headers.
|
||||
*
|
||||
* @param array $response Client response
|
||||
* @return string
|
||||
*/
|
||||
public function findContentType(array $response)
|
||||
{
|
||||
return strtolower($this->getHeader($response, 'Content-Type'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find charset from response headers.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function findCharset()
|
||||
{
|
||||
$result = explode('charset=', $this->content_type);
|
||||
return isset($result[1]) ? $result[1] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header value from a client response.
|
||||
*
|
||||
* @param array $response Client response
|
||||
* @param string $header Header name
|
||||
* @return string
|
||||
*/
|
||||
public function getHeader(array $response, $header)
|
||||
{
|
||||
return isset($response['headers'][$header]) ? $response['headers'][$header] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Last-Modified HTTP header.
|
||||
*
|
||||
* @param string $last_modified Header value
|
||||
* @return $this
|
||||
*/
|
||||
public function setLastModified($last_modified)
|
||||
{
|
||||
$this->last_modified = $last_modified;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the Last-Modified HTTP header.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
return $this->last_modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the Etag HTTP header.
|
||||
*
|
||||
* @param string $etag Etag HTTP header value
|
||||
* @return $this
|
||||
*/
|
||||
public function setEtag($etag)
|
||||
{
|
||||
$this->etag = $etag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Etag HTTP header value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEtag()
|
||||
{
|
||||
return $this->etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the final url value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the url.
|
||||
*
|
||||
* @param $url
|
||||
* @return string
|
||||
*/
|
||||
public function setUrl($url)
|
||||
{
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTTP response status code.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStatusCode()
|
||||
{
|
||||
return $this->status_code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the body of the HTTP response.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content type value from HTTP headers.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContentType()
|
||||
{
|
||||
return $this->content_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the encoding value from HTTP headers.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEncoding()
|
||||
{
|
||||
return $this->encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the remote resource has changed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isModified()
|
||||
{
|
||||
return $this->is_modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if passthrough mode is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPassthroughEnabled()
|
||||
{
|
||||
return $this->passthrough;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set connection timeout.
|
||||
*
|
||||
* @param int $timeout Connection timeout
|
||||
* @return $this
|
||||
*/
|
||||
public function setTimeout($timeout)
|
||||
{
|
||||
$this->timeout = $timeout ?: $this->timeout;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom user agent.
|
||||
*
|
||||
* @param string $user_agent User Agent
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserAgent($user_agent)
|
||||
{
|
||||
$this->user_agent = $user_agent ?: $this->user_agent;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of HTTP redirections.
|
||||
*
|
||||
* @param int $max Maximum
|
||||
* @return $this
|
||||
*/
|
||||
public function setMaxRedirections($max)
|
||||
{
|
||||
$this->max_redirects = $max ?: $this->max_redirects;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum size of the HTTP body.
|
||||
*
|
||||
* @param int $max Maximum
|
||||
* @return $this
|
||||
*/
|
||||
public function setMaxBodySize($max)
|
||||
{
|
||||
$this->max_body_size = $max ?: $this->max_body_size;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the proxy hostname.
|
||||
*
|
||||
* @param string $hostname Proxy hostname
|
||||
* @return $this
|
||||
*/
|
||||
public function setProxyHostname($hostname)
|
||||
{
|
||||
$this->proxy_hostname = $hostname ?: $this->proxy_hostname;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the proxy port.
|
||||
*
|
||||
* @param int $port Proxy port
|
||||
* @return $this
|
||||
*/
|
||||
public function setProxyPort($port)
|
||||
{
|
||||
$this->proxy_port = $port ?: $this->proxy_port;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the proxy username.
|
||||
*
|
||||
* @param string $username Proxy username
|
||||
* @return $this
|
||||
*/
|
||||
public function setProxyUsername($username)
|
||||
{
|
||||
$this->proxy_username = $username ?: $this->proxy_username;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the proxy password.
|
||||
*
|
||||
* @param string $password Password
|
||||
* @return $this
|
||||
*/
|
||||
public function setProxyPassword($password)
|
||||
{
|
||||
$this->proxy_password = $password ?: $this->proxy_password;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the username.
|
||||
*
|
||||
* @param string $username Basic Auth username
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUsername($username)
|
||||
{
|
||||
$this->username = $username ?: $this->username;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the password.
|
||||
*
|
||||
* @param string $password Basic Auth Password
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPassword($password)
|
||||
{
|
||||
$this->password = $password ?: $this->password;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CURL options.
|
||||
*
|
||||
* @param array $options
|
||||
* @return $this
|
||||
*/
|
||||
public function setAdditionalCurlOptions(array $options)
|
||||
{
|
||||
$this->additional_curl_options = $options ?: $this->additional_curl_options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enable the passthrough mode.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function enablePassthroughMode()
|
||||
{
|
||||
$this->passthrough = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the passthrough mode.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function disablePassthroughMode()
|
||||
{
|
||||
$this->passthrough = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set config object.
|
||||
*
|
||||
* @param \PicoFeed\Config\Config $config Config instance
|
||||
* @return $this
|
||||
*/
|
||||
public function setConfig(Config $config)
|
||||
{
|
||||
if ($config !== null) {
|
||||
$this->setTimeout($config->getClientTimeout());
|
||||
$this->setUserAgent($config->getClientUserAgent());
|
||||
$this->setMaxRedirections($config->getMaxRedirections());
|
||||
$this->setMaxBodySize($config->getMaxBodySize());
|
||||
$this->setProxyHostname($config->getProxyHostname());
|
||||
$this->setProxyPort($config->getProxyPort());
|
||||
$this->setProxyUsername($config->getProxyUsername());
|
||||
$this->setProxyPassword($config->getProxyPassword());
|
||||
$this->setAdditionalCurlOptions($config->getAdditionalCurlOptions() ?: array());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the HTTP status code is a redirection
|
||||
*
|
||||
* @access protected
|
||||
* @param integer $code
|
||||
* @return boolean
|
||||
*/
|
||||
public function isRedirection($code)
|
||||
{
|
||||
return $code == 301 || $code == 302 || $code == 303 || $code == 307;
|
||||
}
|
||||
|
||||
public function parseExpiration(HttpHeaders $headers)
|
||||
{
|
||||
try {
|
||||
|
||||
if (isset($headers['Cache-Control'])) {
|
||||
if (preg_match('/s-maxage=(\d+)/', $headers['Cache-Control'], $matches)) {
|
||||
return new DateTime('+' . $matches[1] . ' seconds');
|
||||
} else if (preg_match('/max-age=(\d+)/', $headers['Cache-Control'], $matches)) {
|
||||
return new DateTime('+' . $matches[1] . ' seconds');
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($headers['Expires'])) {
|
||||
return new DateTime($headers['Expires']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Logger::setMessage('Unable to parse expiration date: '.$e->getMessage());
|
||||
}
|
||||
|
||||
return new DateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get expiration date time from "Expires" or "Cache-Control" headers
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
public function getExpiration()
|
||||
{
|
||||
return $this->expiration ?: new DateTime();
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
use PicoFeed\PicoFeedException;
|
||||
|
||||
/**
|
||||
* ClientException Exception.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class ClientException extends PicoFeedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
use PicoFeed\Logging\Logger;
|
||||
|
||||
/**
|
||||
* cURL HTTP client.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Curl extends Client
|
||||
{
|
||||
protected $nbRedirects = 0;
|
||||
|
||||
/**
|
||||
* HTTP response body.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $body = '';
|
||||
|
||||
/**
|
||||
* Body size.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $body_length = 0;
|
||||
|
||||
/**
|
||||
* HTTP response headers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $response_headers = array();
|
||||
|
||||
/**
|
||||
* Counter on the number of header received.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $response_headers_count = 0;
|
||||
|
||||
/**
|
||||
* cURL callback to read the HTTP body.
|
||||
*
|
||||
* If the function return -1, curl stop to read the HTTP response
|
||||
*
|
||||
* @param resource $ch cURL handler
|
||||
* @param string $buffer Chunk of data
|
||||
*
|
||||
* @return int Length of the buffer
|
||||
*/
|
||||
public function readBody($ch, $buffer)
|
||||
{
|
||||
$length = strlen($buffer);
|
||||
$this->body_length += $length;
|
||||
|
||||
if ($this->body_length > $this->max_body_size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$this->body .= $buffer;
|
||||
|
||||
return $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* cURL callback to read HTTP headers.
|
||||
*
|
||||
* @param resource $ch cURL handler
|
||||
* @param string $buffer Header line
|
||||
*
|
||||
* @return int Length of the buffer
|
||||
*/
|
||||
public function readHeaders($ch, $buffer)
|
||||
{
|
||||
$length = strlen($buffer);
|
||||
|
||||
if ($buffer === "\r\n" || $buffer === "\n") {
|
||||
++$this->response_headers_count;
|
||||
} else {
|
||||
if (!isset($this->response_headers[$this->response_headers_count])) {
|
||||
$this->response_headers[$this->response_headers_count] = '';
|
||||
}
|
||||
|
||||
$this->response_headers[$this->response_headers_count] .= $buffer;
|
||||
}
|
||||
|
||||
return $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* cURL callback to passthrough the HTTP body to the client.
|
||||
*
|
||||
* If the function return -1, curl stop to read the HTTP response
|
||||
*
|
||||
* @param resource $ch cURL handler
|
||||
* @param string $buffer Chunk of data
|
||||
*
|
||||
* @return int Length of the buffer
|
||||
*/
|
||||
public function passthroughBody($ch, $buffer)
|
||||
{
|
||||
// do it only at the beginning of a transmission
|
||||
if ($this->body_length === 0) {
|
||||
list($status, $headers) = HttpHeaders::parse(explode("\n", $this->response_headers[$this->response_headers_count - 1]));
|
||||
|
||||
if ($this->isRedirection($status)) {
|
||||
return $this->handleRedirection($headers['Location']);
|
||||
}
|
||||
|
||||
if (isset($headers['Content-Type'])) {
|
||||
header('Content-Type:' .$headers['Content-Type']);
|
||||
}
|
||||
}
|
||||
|
||||
$length = strlen($buffer);
|
||||
$this->body_length += $length;
|
||||
|
||||
echo $buffer;
|
||||
|
||||
return $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare HTTP headers.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function prepareHeaders()
|
||||
{
|
||||
$headers = array(
|
||||
'Connection: close',
|
||||
);
|
||||
|
||||
if ($this->etag) {
|
||||
$headers[] = 'If-None-Match: '.$this->etag;
|
||||
$headers[] = 'A-IM: feed';
|
||||
}
|
||||
|
||||
if ($this->last_modified) {
|
||||
$headers[] = 'If-Modified-Since: '.$this->last_modified;
|
||||
}
|
||||
|
||||
$headers = array_merge($headers, $this->request_headers);
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare curl proxy context.
|
||||
*
|
||||
* @param resource $ch
|
||||
*
|
||||
* @return resource $ch
|
||||
*/
|
||||
private function prepareProxyContext($ch)
|
||||
{
|
||||
if ($this->proxy_hostname) {
|
||||
Logger::setMessage(get_called_class().' Proxy: '.$this->proxy_hostname.':'.$this->proxy_port);
|
||||
|
||||
curl_setopt($ch, CURLOPT_PROXYPORT, $this->proxy_port);
|
||||
curl_setopt($ch, CURLOPT_PROXYTYPE, 'HTTP');
|
||||
curl_setopt($ch, CURLOPT_PROXY, $this->proxy_hostname);
|
||||
|
||||
if ($this->proxy_username) {
|
||||
Logger::setMessage(get_called_class().' Proxy credentials: Yes');
|
||||
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->proxy_username.':'.$this->proxy_password);
|
||||
} else {
|
||||
Logger::setMessage(get_called_class().' Proxy credentials: No');
|
||||
}
|
||||
}
|
||||
|
||||
return $ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare curl auth context.
|
||||
*
|
||||
* @param resource $ch
|
||||
*
|
||||
* @return resource $ch
|
||||
*/
|
||||
private function prepareAuthContext($ch)
|
||||
{
|
||||
if ($this->username && $this->password) {
|
||||
curl_setopt($ch, CURLOPT_USERPWD, $this->username.':'.$this->password);
|
||||
}
|
||||
|
||||
return $ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set write/header functions.
|
||||
*
|
||||
* @param resource $ch
|
||||
*
|
||||
* @return resource $ch
|
||||
*/
|
||||
private function prepareDownloadMode($ch)
|
||||
{
|
||||
$this->body = '';
|
||||
$this->response_headers = array();
|
||||
$this->response_headers_count = 0;
|
||||
$write_function = 'readBody';
|
||||
$header_function = 'readHeaders';
|
||||
|
||||
if ($this->isPassthroughEnabled()) {
|
||||
$write_function = 'passthroughBody';
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, $write_function));
|
||||
curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, $header_function));
|
||||
|
||||
return $ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set additional CURL options.
|
||||
*
|
||||
* @param resource $ch
|
||||
*
|
||||
* @return resource $ch
|
||||
*/
|
||||
private function prepareAdditionalCurlOptions($ch){
|
||||
foreach( $this->additional_curl_options as $c_op => $c_val ){
|
||||
curl_setopt($ch, $c_op, $c_val);
|
||||
}
|
||||
return $ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare curl context.
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
private function prepareContext()
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $this->url);
|
||||
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, $this->user_agent);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->prepareHeaders());
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
||||
curl_setopt($ch, CURLOPT_ENCODING, '');
|
||||
curl_setopt($ch, CURLOPT_COOKIEJAR, 'php://memory');
|
||||
curl_setopt($ch, CURLOPT_COOKIEFILE, 'php://memory');
|
||||
|
||||
// Disable SSLv3 by enforcing TLSv1.x for curl >= 7.34.0 and < 7.39.0.
|
||||
// Versions prior to 7.34 and at least when compiled against openssl
|
||||
// interpret this parameter as "limit to TLSv1.0" which fails for sites
|
||||
// which enforce TLS 1.1+.
|
||||
// Starting with curl 7.39.0 SSLv3 is disabled by default.
|
||||
$version = curl_version();
|
||||
if ($version['version_number'] >= 467456 && $version['version_number'] < 468736) {
|
||||
curl_setopt($ch, CURLOPT_SSLVERSION, 1);
|
||||
}
|
||||
|
||||
$ch = $this->prepareDownloadMode($ch);
|
||||
$ch = $this->prepareProxyContext($ch);
|
||||
$ch = $this->prepareAuthContext($ch);
|
||||
$ch = $this->prepareAdditionalCurlOptions($ch);
|
||||
|
||||
return $ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute curl context.
|
||||
*/
|
||||
private function executeContext()
|
||||
{
|
||||
$ch = $this->prepareContext();
|
||||
curl_exec($ch);
|
||||
|
||||
Logger::setMessage(get_called_class().' cURL total time: '.curl_getinfo($ch, CURLINFO_TOTAL_TIME));
|
||||
Logger::setMessage(get_called_class().' cURL dns lookup time: '.curl_getinfo($ch, CURLINFO_NAMELOOKUP_TIME));
|
||||
Logger::setMessage(get_called_class().' cURL connect time: '.curl_getinfo($ch, CURLINFO_CONNECT_TIME));
|
||||
Logger::setMessage(get_called_class().' cURL speed download: '.curl_getinfo($ch, CURLINFO_SPEED_DOWNLOAD));
|
||||
Logger::setMessage(get_called_class().' cURL effective url: '.curl_getinfo($ch, CURLINFO_EFFECTIVE_URL));
|
||||
|
||||
$curl_errno = curl_errno($ch);
|
||||
|
||||
if ($curl_errno) {
|
||||
Logger::setMessage(get_called_class().' cURL error: '.curl_error($ch));
|
||||
curl_close($ch);
|
||||
|
||||
$this->handleError($curl_errno);
|
||||
}
|
||||
|
||||
// Update the url if there where redirects
|
||||
$this->url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||||
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the HTTP request.
|
||||
*
|
||||
* @return array HTTP response ['body' => ..., 'status' => ..., 'headers' => ...]
|
||||
*/
|
||||
public function doRequest()
|
||||
{
|
||||
$this->executeContext();
|
||||
|
||||
list($status, $headers) = HttpHeaders::parse(explode("\n", $this->response_headers[$this->response_headers_count - 1]));
|
||||
|
||||
if ($this->isRedirection($status)) {
|
||||
if (empty($headers['Location'])) {
|
||||
$status = 200;
|
||||
} else {
|
||||
return $this->handleRedirection($headers['Location']);
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'status' => $status,
|
||||
'body' => $this->body,
|
||||
'headers' => $headers,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle HTTP redirects
|
||||
*
|
||||
* @param string $location Redirected URL
|
||||
* @return array
|
||||
* @throws MaxRedirectException
|
||||
*/
|
||||
private function handleRedirection($location)
|
||||
{
|
||||
$result = array();
|
||||
$this->url = Url::resolve($location, $this->url);
|
||||
$this->body = '';
|
||||
$this->body_length = 0;
|
||||
$this->response_headers = array();
|
||||
$this->response_headers_count = 0;
|
||||
|
||||
while (true) {
|
||||
$this->nbRedirects++;
|
||||
|
||||
if ($this->nbRedirects >= $this->max_redirects) {
|
||||
throw new MaxRedirectException('Maximum number of redirections reached');
|
||||
}
|
||||
|
||||
$result = $this->doRequest();
|
||||
|
||||
if ($this->isRedirection($result['status'])) {
|
||||
$this->url = Url::resolve($result['headers']['Location'], $this->url);
|
||||
$this->body = '';
|
||||
$this->body_length = 0;
|
||||
$this->response_headers = array();
|
||||
$this->response_headers_count = 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle cURL errors (throw individual exceptions).
|
||||
*
|
||||
* We don't use constants because they are not necessary always available
|
||||
* (depends of the version of libcurl linked to php)
|
||||
*
|
||||
* @see http://curl.haxx.se/libcurl/c/libcurl-errors.html
|
||||
*
|
||||
* @param int $errno cURL error code
|
||||
* @throws InvalidCertificateException
|
||||
* @throws InvalidUrlException
|
||||
* @throws MaxRedirectException
|
||||
* @throws MaxSizeException
|
||||
* @throws TimeoutException
|
||||
*/
|
||||
private function handleError($errno)
|
||||
{
|
||||
switch ($errno) {
|
||||
case 78: // CURLE_REMOTE_FILE_NOT_FOUND
|
||||
throw new InvalidUrlException('Resource not found', $errno);
|
||||
case 6: // CURLE_COULDNT_RESOLVE_HOST
|
||||
throw new InvalidUrlException('Unable to resolve hostname', $errno);
|
||||
case 7: // CURLE_COULDNT_CONNECT
|
||||
throw new InvalidUrlException('Unable to connect to the remote host', $errno);
|
||||
case 23: // CURLE_WRITE_ERROR
|
||||
throw new MaxSizeException('Maximum response size exceeded', $errno);
|
||||
case 28: // CURLE_OPERATION_TIMEDOUT
|
||||
throw new TimeoutException('Operation timeout', $errno);
|
||||
case 35: // CURLE_SSL_CONNECT_ERROR
|
||||
case 51: // CURLE_PEER_FAILED_VERIFICATION
|
||||
case 58: // CURLE_SSL_CERTPROBLEM
|
||||
case 60: // CURLE_SSL_CACERT
|
||||
case 59: // CURLE_SSL_CIPHER
|
||||
case 64: // CURLE_USE_SSL_FAILED
|
||||
case 66: // CURLE_SSL_ENGINE_INITFAILED
|
||||
case 77: // CURLE_SSL_CACERT_BADFILE
|
||||
case 83: // CURLE_SSL_ISSUER_ERROR
|
||||
$msg = 'Invalid SSL certificate caused by CURL error number ' . $errno;
|
||||
throw new InvalidCertificateException($msg, $errno);
|
||||
case 47: // CURLE_TOO_MANY_REDIRECTS
|
||||
throw new MaxRedirectException('Maximum number of redirections reached', $errno);
|
||||
case 63: // CURLE_FILESIZE_EXCEEDED
|
||||
throw new MaxSizeException('Maximum response size exceeded', $errno);
|
||||
default:
|
||||
throw new InvalidUrlException('Unable to fetch the URL', $errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
/**
|
||||
* @author Bernhard Posselt
|
||||
*/
|
||||
class ForbiddenException extends ClientException
|
||||
{
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
use ArrayAccess;
|
||||
use PicoFeed\Logging\Logger;
|
||||
|
||||
/**
|
||||
* Class to handle HTTP headers case insensitivity.
|
||||
*
|
||||
* @author Bernhard Posselt
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class HttpHeaders implements ArrayAccess
|
||||
{
|
||||
private $headers = array();
|
||||
|
||||
public function __construct(array $headers)
|
||||
{
|
||||
foreach ($headers as $key => $value) {
|
||||
$this->headers[strtolower($key)] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->offsetExists($offset) ? $this->headers[strtolower($offset)] : '';
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->headers[strtolower($offset)] = $value;
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->headers[strtolower($offset)]);
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->headers[strtolower($offset)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse HTTP headers.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param array $lines List of headers
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function parse(array $lines)
|
||||
{
|
||||
$status = 0;
|
||||
$headers = array();
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, 'HTTP/1') === 0) {
|
||||
$headers = array();
|
||||
$status = (int) substr($line, 9, 3);
|
||||
} elseif (strpos($line, ': ') !== false) {
|
||||
list($name, $value) = explode(': ', $line);
|
||||
if ($value) {
|
||||
$headers[trim($name)] = trim($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger::setMessage(get_called_class().' HTTP status code: '.$status);
|
||||
|
||||
foreach ($headers as $name => $value) {
|
||||
Logger::setMessage(get_called_class().' HTTP header: '.$name.' => '.$value);
|
||||
}
|
||||
|
||||
return array($status, new self($headers));
|
||||
}
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
/**
|
||||
* InvalidCertificateException Exception.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class InvalidCertificateException extends ClientException
|
||||
{
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
/**
|
||||
* InvalidUrlException Exception.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class InvalidUrlException extends ClientException
|
||||
{
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
/**
|
||||
* MaxRedirectException Exception.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class MaxRedirectException extends ClientException
|
||||
{
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
/**
|
||||
* MaxSizeException Exception.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class MaxSizeException extends ClientException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
use PicoFeed\Logging\Logger;
|
||||
|
||||
/**
|
||||
* Stream context HTTP client.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Stream extends Client
|
||||
{
|
||||
/**
|
||||
* Prepare HTTP headers.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function prepareHeaders()
|
||||
{
|
||||
$headers = array(
|
||||
'Connection: close',
|
||||
'User-Agent: '.$this->user_agent,
|
||||
);
|
||||
|
||||
// disable compression in passthrough mode. It could result in double
|
||||
// compressed content which isn't decodeable by browsers
|
||||
if (function_exists('gzdecode') && !$this->isPassthroughEnabled()) {
|
||||
$headers[] = 'Accept-Encoding: gzip';
|
||||
}
|
||||
|
||||
if ($this->etag) {
|
||||
$headers[] = 'If-None-Match: '.$this->etag;
|
||||
$headers[] = 'A-IM: feed';
|
||||
}
|
||||
|
||||
if ($this->last_modified) {
|
||||
$headers[] = 'If-Modified-Since: '.$this->last_modified;
|
||||
}
|
||||
|
||||
if ($this->proxy_username) {
|
||||
$headers[] = 'Proxy-Authorization: Basic '.base64_encode($this->proxy_username.':'.$this->proxy_password);
|
||||
}
|
||||
|
||||
if ($this->username && $this->password) {
|
||||
$headers[] = 'Authorization: Basic '.base64_encode($this->username.':'.$this->password);
|
||||
}
|
||||
|
||||
$headers = array_merge($headers, $this->request_headers);
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the final URL from location headers.
|
||||
*
|
||||
* @param array $headers List of HTTP response header
|
||||
*/
|
||||
private function setEffectiveUrl($headers)
|
||||
{
|
||||
foreach ($headers as $header) {
|
||||
if (stripos($header, 'Location') === 0) {
|
||||
list(, $value) = explode(': ', $header);
|
||||
|
||||
$this->url = Url::resolve($value, $this->url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare stream context.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepareContext()
|
||||
{
|
||||
$context = array(
|
||||
'http' => array(
|
||||
'method' => 'GET',
|
||||
'protocol_version' => 1.1,
|
||||
'timeout' => $this->timeout,
|
||||
'max_redirects' => $this->max_redirects,
|
||||
),
|
||||
);
|
||||
|
||||
if ($this->proxy_hostname) {
|
||||
Logger::setMessage(get_called_class().' Proxy: '.$this->proxy_hostname.':'.$this->proxy_port);
|
||||
|
||||
$context['http']['proxy'] = 'tcp://'.$this->proxy_hostname.':'.$this->proxy_port;
|
||||
$context['http']['request_fulluri'] = true;
|
||||
|
||||
if ($this->proxy_username) {
|
||||
Logger::setMessage(get_called_class().' Proxy credentials: Yes');
|
||||
} else {
|
||||
Logger::setMessage(get_called_class().' Proxy credentials: No');
|
||||
}
|
||||
}
|
||||
|
||||
$context['http']['header'] = implode("\r\n", $this->prepareHeaders());
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the HTTP request.
|
||||
*
|
||||
* @return array HTTP response ['body' => ..., 'status' => ..., 'headers' => ...]
|
||||
* @throws InvalidUrlException
|
||||
* @throws MaxSizeException
|
||||
* @throws TimeoutException
|
||||
*/
|
||||
public function doRequest()
|
||||
{
|
||||
$body = '';
|
||||
|
||||
// Create context
|
||||
$context = stream_context_create($this->prepareContext());
|
||||
|
||||
// Make HTTP request
|
||||
$stream = @fopen($this->url, 'r', false, $context);
|
||||
if (!is_resource($stream)) {
|
||||
throw new InvalidUrlException('Unable to establish a connection');
|
||||
}
|
||||
|
||||
// Get HTTP headers response
|
||||
$metadata = stream_get_meta_data($stream);
|
||||
list($status, $headers) = HttpHeaders::parse($metadata['wrapper_data']);
|
||||
|
||||
if ($this->isPassthroughEnabled()) {
|
||||
header(':', true, $status);
|
||||
|
||||
if (isset($headers['Content-Type'])) {
|
||||
header('Content-Type: '.$headers['Content-Type']);
|
||||
}
|
||||
|
||||
fpassthru($stream);
|
||||
} else {
|
||||
// Get the entire body until the max size
|
||||
$body = stream_get_contents($stream, $this->max_body_size + 1);
|
||||
|
||||
// If the body size is too large abort everything
|
||||
if (strlen($body) > $this->max_body_size) {
|
||||
throw new MaxSizeException('Content size too large');
|
||||
}
|
||||
|
||||
if ($metadata['timed_out']) {
|
||||
throw new TimeoutException('Operation timeout');
|
||||
}
|
||||
}
|
||||
|
||||
fclose($stream);
|
||||
|
||||
$this->setEffectiveUrl($metadata['wrapper_data']);
|
||||
|
||||
return array(
|
||||
'status' => $status,
|
||||
'body' => $this->decodeBody($body, $headers),
|
||||
'headers' => $headers,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode body response according to the HTTP headers.
|
||||
*
|
||||
* @param string $body Raw body
|
||||
* @param HttpHeaders $headers HTTP headers
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function decodeBody($body, HttpHeaders $headers)
|
||||
{
|
||||
if (isset($headers['Transfer-Encoding']) && $headers['Transfer-Encoding'] === 'chunked') {
|
||||
$body = $this->decodeChunked($body);
|
||||
}
|
||||
|
||||
if (isset($headers['Content-Encoding']) && $headers['Content-Encoding'] === 'gzip') {
|
||||
$body = gzdecode($body);
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a chunked body.
|
||||
*
|
||||
* @param string $str Raw body
|
||||
*
|
||||
* @return string Decoded body
|
||||
*/
|
||||
public function decodeChunked($str)
|
||||
{
|
||||
for ($result = ''; !empty($str); $str = trim($str)) {
|
||||
|
||||
// Get the chunk length
|
||||
$pos = strpos($str, "\r\n");
|
||||
$len = hexdec(substr($str, 0, $pos));
|
||||
|
||||
// Append the chunk to the result
|
||||
$result .= substr($str, $pos + 2, $len);
|
||||
$str = substr($str, $pos + 2 + $len);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
/**
|
||||
* TimeoutException Exception.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TimeoutException extends ClientException
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
/**
|
||||
* @author Bernhard Posselt
|
||||
*/
|
||||
class UnauthorizedException extends ClientException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Client;
|
||||
|
||||
/**
|
||||
* URL class.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Url
|
||||
{
|
||||
/**
|
||||
* URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $url = '';
|
||||
|
||||
/**
|
||||
* URL components.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $components = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $url URL
|
||||
*/
|
||||
public function __construct($url)
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->components = parse_url($url) ?: array();
|
||||
|
||||
// Issue with PHP < 5.4.7 and protocol relative url
|
||||
if (version_compare(PHP_VERSION, '5.4.7', '<') && $this->isProtocolRelative()) {
|
||||
$pos = strpos($this->components['path'], '/', 2);
|
||||
|
||||
if ($pos === false) {
|
||||
$pos = strlen($this->components['path']);
|
||||
}
|
||||
|
||||
$this->components['host'] = substr($this->components['path'], 2, $pos - 2);
|
||||
$this->components['path'] = substr($this->components['path'], $pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut method to get an absolute url from relative url.
|
||||
*
|
||||
* @static
|
||||
* @param mixed $item_url Unknown url (can be relative or not)
|
||||
* @param mixed $website_url Website url
|
||||
* @return string
|
||||
*/
|
||||
public static function resolve($item_url, $website_url)
|
||||
{
|
||||
$link = is_string($item_url) ? new self($item_url) : $item_url;
|
||||
$website = is_string($website_url) ? new self($website_url) : $website_url;
|
||||
|
||||
if ($link->isRelativeUrl()) {
|
||||
if ($link->isRelativePath()) {
|
||||
return $link->getAbsoluteUrl($website->getBaseUrl($website->getBasePath()));
|
||||
}
|
||||
|
||||
return $link->getAbsoluteUrl($website->getBaseUrl());
|
||||
} elseif ($link->isProtocolRelative()) {
|
||||
$link->setScheme($website->getScheme());
|
||||
}
|
||||
|
||||
return $link->getAbsoluteUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut method to get a base url.
|
||||
*
|
||||
* @static
|
||||
* @param string $url
|
||||
* @return string
|
||||
*/
|
||||
public static function base($url)
|
||||
{
|
||||
$link = new self($url);
|
||||
|
||||
return $link->getBaseUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base URL.
|
||||
*
|
||||
* @param string $suffix Add a suffix to the url
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseUrl($suffix = '')
|
||||
{
|
||||
return $this->hasHost() ? $this->getScheme('://').$this->getHost().$this->getPort(':').$suffix : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the absolute URL.
|
||||
*
|
||||
* @param string $base_url Use this url as base url
|
||||
* @return string
|
||||
*/
|
||||
public function getAbsoluteUrl($base_url = '')
|
||||
{
|
||||
if ($base_url) {
|
||||
$base = new self($base_url);
|
||||
$url = $base->getAbsoluteUrl().substr($this->getFullPath(), 1);
|
||||
} else {
|
||||
$url = $this->hasHost() ? $this->getBaseUrl().$this->getFullPath() : '';
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the url is relative.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRelativeUrl()
|
||||
{
|
||||
return !$this->hasScheme() && !$this->isProtocolRelative();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the path is relative.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRelativePath()
|
||||
{
|
||||
$path = $this->getPath();
|
||||
|
||||
return empty($path) || $path{0}
|
||||
!== '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the path of a URI.
|
||||
*
|
||||
* Imported from Guzzle library: https://github.com/guzzle/psr7/blob/master/src/Uri.php#L568-L582
|
||||
*
|
||||
* @param $path
|
||||
* @param string $charUnreserved
|
||||
* @param string $charSubDelims
|
||||
* @return string
|
||||
*/
|
||||
public function filterPath($path, $charUnreserved = 'a-zA-Z0-9_\-\.~', $charSubDelims = '!\$&\'\(\)\*\+,;=')
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/(?:[^'.$charUnreserved.$charSubDelims.':@\/%]+|%(?![A-Fa-f0-9]{2}))/',
|
||||
function (array $matches) { return rawurlencode($matches[0]); },
|
||||
$path
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->filterPath(empty($this->components['path']) ? '' : $this->components['path']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBasePath()
|
||||
{
|
||||
$current_path = $this->getPath();
|
||||
|
||||
$path = $this->isRelativePath() ? '/' : '';
|
||||
$path .= substr($current_path, -1) === '/' ? $current_path : dirname($current_path);
|
||||
|
||||
return preg_replace('/\\\\\/|\/\//', '/', $path.'/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path (path + querystring + fragment).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFullPath()
|
||||
{
|
||||
$path = $this->isRelativePath() ? '/' : '';
|
||||
$path .= $this->getPath();
|
||||
$path .= empty($this->components['query']) ? '' : '?'.$this->components['query'];
|
||||
$path .= empty($this->components['fragment']) ? '' : '#'.$this->components['fragment'];
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hostname.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHost()
|
||||
{
|
||||
return empty($this->components['host']) ? '' : $this->components['host'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the url has a hostname.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasHost()
|
||||
{
|
||||
return !empty($this->components['host']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scheme.
|
||||
*
|
||||
* @param string $suffix Suffix to add when there is a scheme
|
||||
* @return string
|
||||
*/
|
||||
public function getScheme($suffix = '')
|
||||
{
|
||||
return ($this->hasScheme() ? $this->components['scheme'] : 'http').$suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scheme.
|
||||
*
|
||||
* @param string $scheme Set a scheme
|
||||
* @return $this
|
||||
*/
|
||||
public function setScheme($scheme)
|
||||
{
|
||||
$this->components['scheme'] = $scheme;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the url has a scheme.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasScheme()
|
||||
{
|
||||
return !empty($this->components['scheme']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port.
|
||||
*
|
||||
* @param string $prefix Prefix to add when there is a port
|
||||
* @return string
|
||||
*/
|
||||
public function getPort($prefix = '')
|
||||
{
|
||||
return $this->hasPort() ? $prefix.$this->components['port'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the url has a port.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPort()
|
||||
{
|
||||
return !empty($this->components['port']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the url is protocol relative (start with //).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isProtocolRelative()
|
||||
{
|
||||
return strpos($this->url, '//') === 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Config;
|
||||
|
||||
/**
|
||||
* Config class.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*
|
||||
* @method \PicoFeed\Config\Config setAdditionalCurlOptions(array $options)
|
||||
* @method \PicoFeed\Config\Config setClientTimeout(integer $value)
|
||||
* @method \PicoFeed\Config\Config setClientUserAgent(string $value)
|
||||
* @method \PicoFeed\Config\Config setMaxRedirections(integer $value)
|
||||
* @method \PicoFeed\Config\Config setMaxRecursions(integer $value)
|
||||
* @method \PicoFeed\Config\Config setMaxBodySize(integer $value)
|
||||
* @method \PicoFeed\Config\Config setProxyHostname(string $value)
|
||||
* @method \PicoFeed\Config\Config setProxyPort(integer $value)
|
||||
* @method \PicoFeed\Config\Config setProxyUsername(string $value)
|
||||
* @method \PicoFeed\Config\Config setProxyPassword(string $value)
|
||||
* @method \PicoFeed\Config\Config setGrabberRulesFolder(string $value)
|
||||
* @method \PicoFeed\Config\Config setGrabberTimeout(integer $value)
|
||||
* @method \PicoFeed\Config\Config setGrabberUserAgent(string $value)
|
||||
* @method \PicoFeed\Config\Config setParserHashAlgo(string $value)
|
||||
* @method \PicoFeed\Config\Config setContentFiltering(boolean $value)
|
||||
* @method \PicoFeed\Config\Config setTimezone(string $value)
|
||||
* @method \PicoFeed\Config\Config setFilterIframeWhitelist(array $value)
|
||||
* @method \PicoFeed\Config\Config setFilterIntegerAttributes(array $value)
|
||||
* @method \PicoFeed\Config\Config setFilterAttributeOverrides(array $value)
|
||||
* @method \PicoFeed\Config\Config setFilterRequiredAttributes(array $value)
|
||||
* @method \PicoFeed\Config\Config setFilterMediaBlacklist(array $value)
|
||||
* @method \PicoFeed\Config\Config setFilterMediaAttributes(array $value)
|
||||
* @method \PicoFeed\Config\Config setFilterSchemeWhitelist(array $value)
|
||||
* @method \PicoFeed\Config\Config setFilterWhitelistedTags(array $value)
|
||||
* @method \PicoFeed\Config\Config setFilterBlacklistedTags(array $value)
|
||||
* @method \PicoFeed\Config\Config setFilterImageProxyUrl($value)
|
||||
* @method \PicoFeed\Config\Config setFilterImageProxyCallback($closure)
|
||||
* @method \PicoFeed\Config\Config setFilterImageProxyProtocol($value)
|
||||
* @method integer getClientTimeout()
|
||||
* @method string getClientUserAgent()
|
||||
* @method integer getMaxRedirections()
|
||||
* @method integer getMaxRecursions()
|
||||
* @method integer getMaxBodySize()
|
||||
* @method string getProxyHostname()
|
||||
* @method integer getProxyPort()
|
||||
* @method string getProxyUsername()
|
||||
* @method string getProxyPassword()
|
||||
* @method string getGrabberRulesFolder()
|
||||
* @method integer getGrabberTimeout()
|
||||
* @method string getGrabberUserAgent()
|
||||
* @method string getParserHashAlgo()
|
||||
* @method boolean getContentFiltering(bool $default_value)
|
||||
* @method string getTimezone()
|
||||
* @method array getFilterIframeWhitelist(array $default_value)
|
||||
* @method array getFilterIntegerAttributes(array $default_value)
|
||||
* @method array getFilterAttributeOverrides(array $default_value)
|
||||
* @method array getFilterRequiredAttributes(array $default_value)
|
||||
* @method array getFilterMediaBlacklist(array $default_value)
|
||||
* @method array getFilterMediaAttributes(array $default_value)
|
||||
* @method array getFilterSchemeWhitelist(array $default_value)
|
||||
* @method array getFilterWhitelistedTags(array $default_value)
|
||||
* @method array getFilterBlacklistedTags(array $default_value)
|
||||
* @method string getFilterImageProxyUrl()
|
||||
* @method \Closure getFilterImageProxyCallback()
|
||||
* @method string getFilterImageProxyProtocol()
|
||||
* @method array getAdditionalCurlOptions()
|
||||
*/
|
||||
class Config
|
||||
{
|
||||
/**
|
||||
* Contains all parameters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $container = array();
|
||||
|
||||
/**
|
||||
* Magic method to have any kind of setters or getters.
|
||||
*
|
||||
* @param string $name Getter/Setter name
|
||||
* @param array $arguments Method arguments
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($name, array $arguments)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
$prefix = substr($name, 0, 3);
|
||||
$parameter = substr($name, 3);
|
||||
|
||||
if ($prefix === 'set' && isset($arguments[0])) {
|
||||
$this->container[$parameter] = $arguments[0];
|
||||
|
||||
return $this;
|
||||
} elseif ($prefix === 'get') {
|
||||
$default_value = isset($arguments[0]) ? $arguments[0] : null;
|
||||
|
||||
return isset($this->container[$parameter]) ? $this->container[$parameter] : $default_value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Encoding;
|
||||
|
||||
/**
|
||||
* Encoding class.
|
||||
*/
|
||||
class Encoding
|
||||
{
|
||||
public static function convert($input, $encoding)
|
||||
{
|
||||
if ($encoding === 'utf-8' || $encoding === '') {
|
||||
return $input;
|
||||
}
|
||||
|
||||
// suppress all notices since it isn't possible to silence only the
|
||||
// notice "Wrong charset, conversion from $in_encoding to $out_encoding is not allowed"
|
||||
set_error_handler(function () {}, E_NOTICE);
|
||||
|
||||
// convert input to utf-8 and strip invalid characters
|
||||
$value = iconv($encoding, 'UTF-8//IGNORE', $input);
|
||||
|
||||
// stop silencing of notices
|
||||
restore_error_handler();
|
||||
|
||||
// return input if something went wrong, maybe it's usable anyway
|
||||
if ($value === false) {
|
||||
return $input;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
+700
@@ -0,0 +1,700 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Filter;
|
||||
|
||||
use PicoFeed\Client\Url;
|
||||
|
||||
/**
|
||||
* Attribute Filter class.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Attribute
|
||||
{
|
||||
/**
|
||||
* Image proxy url.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $image_proxy_url = '';
|
||||
|
||||
/**
|
||||
* Image proxy callback.
|
||||
*
|
||||
* @var \Closure|null
|
||||
*/
|
||||
private $image_proxy_callback = null;
|
||||
|
||||
/**
|
||||
* limits the image proxy usage to this protocol.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $image_proxy_limit_protocol = '';
|
||||
|
||||
/**
|
||||
* Tags and attribute whitelist.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $attribute_whitelist = array(
|
||||
'audio' => array('controls', 'src'),
|
||||
'video' => array('poster', 'controls', 'height', 'width', 'src'),
|
||||
'source' => array('src', 'type'),
|
||||
'dt' => array(),
|
||||
'dd' => array(),
|
||||
'dl' => array(),
|
||||
'table' => array(),
|
||||
'caption' => array(),
|
||||
'tr' => array(),
|
||||
'th' => array(),
|
||||
'td' => array(),
|
||||
'tbody' => array(),
|
||||
'thead' => array(),
|
||||
'h1' => array(),
|
||||
'h2' => array(),
|
||||
'h3' => array(),
|
||||
'h4' => array(),
|
||||
'h5' => array(),
|
||||
'h6' => array(),
|
||||
'strong' => array(),
|
||||
'em' => array(),
|
||||
'code' => array(),
|
||||
'pre' => array(),
|
||||
'blockquote' => array(),
|
||||
'p' => array(),
|
||||
'ul' => array(),
|
||||
'li' => array(),
|
||||
'ol' => array(),
|
||||
'br' => array(),
|
||||
'del' => array(),
|
||||
'a' => array('href'),
|
||||
'img' => array('src', 'title', 'alt'),
|
||||
'figure' => array(),
|
||||
'figcaption' => array(),
|
||||
'cite' => array(),
|
||||
'time' => array('datetime'),
|
||||
'abbr' => array('title'),
|
||||
'iframe' => array('width', 'height', 'frameborder', 'src', 'allowfullscreen'),
|
||||
'q' => array('cite'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Scheme whitelist.
|
||||
*
|
||||
* For a complete list go to http://en.wikipedia.org/wiki/URI_scheme
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $scheme_whitelist = array(
|
||||
'bitcoin:',
|
||||
'callto:',
|
||||
'ed2k://',
|
||||
'facetime://',
|
||||
'feed:',
|
||||
'ftp://',
|
||||
'geo:',
|
||||
'git://',
|
||||
'http://',
|
||||
'https://',
|
||||
'irc://',
|
||||
'irc6://',
|
||||
'ircs://',
|
||||
'jabber:',
|
||||
'magnet:',
|
||||
'mailto:',
|
||||
'nntp://',
|
||||
'rtmp://',
|
||||
'sftp://',
|
||||
'sip:',
|
||||
'sips:',
|
||||
'skype:',
|
||||
'smb://',
|
||||
'sms:',
|
||||
'spotify:',
|
||||
'ssh:',
|
||||
'steam:',
|
||||
'svn://',
|
||||
'tel:',
|
||||
);
|
||||
|
||||
/**
|
||||
* Iframe source whitelist, everything else is ignored.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $iframe_whitelist = array(
|
||||
'http://www.youtube.com',
|
||||
'https://www.youtube.com',
|
||||
'http://player.vimeo.com',
|
||||
'https://player.vimeo.com',
|
||||
'http://www.dailymotion.com',
|
||||
'https://www.dailymotion.com',
|
||||
'http://vk.com',
|
||||
'https://vk.com',
|
||||
);
|
||||
|
||||
/**
|
||||
* Blacklisted resources.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $media_blacklist = array(
|
||||
'api.flattr.com',
|
||||
'feeds.feedburner.com',
|
||||
'share.feedsportal.com',
|
||||
'da.feedsportal.com',
|
||||
'rc.feedsportal.com',
|
||||
'rss.feedsportal.com',
|
||||
'res.feedsportal.com',
|
||||
'res1.feedsportal.com',
|
||||
'res2.feedsportal.com',
|
||||
'res3.feedsportal.com',
|
||||
'pi.feedsportal.com',
|
||||
'rss.nytimes.com',
|
||||
'feeds.wordpress.com',
|
||||
'stats.wordpress.com',
|
||||
'rss.cnn.com',
|
||||
'twitter.com/home?status=',
|
||||
'twitter.com/share',
|
||||
'twitter_icon_large.png',
|
||||
'www.facebook.com/sharer.php',
|
||||
'facebook_icon_large.png',
|
||||
'plus.google.com/share',
|
||||
'www.gstatic.com/images/icons/gplus-16.png',
|
||||
'www.gstatic.com/images/icons/gplus-32.png',
|
||||
'www.gstatic.com/images/icons/gplus-64.png',
|
||||
);
|
||||
|
||||
/**
|
||||
* Attributes used for external resources.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $media_attributes = array(
|
||||
'src',
|
||||
'href',
|
||||
'poster',
|
||||
);
|
||||
|
||||
/**
|
||||
* Attributes that must be integer.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $integer_attributes = array(
|
||||
'width',
|
||||
'height',
|
||||
'frameborder',
|
||||
);
|
||||
|
||||
/**
|
||||
* Mandatory attributes for specified tags.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $required_attributes = array(
|
||||
'a' => array('href'),
|
||||
'img' => array('src'),
|
||||
'iframe' => array('src'),
|
||||
'audio' => array('src'),
|
||||
'source' => array('src'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Add attributes to specified tags.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $add_attributes = array(
|
||||
'a' => array('rel' => 'noreferrer', 'target' => '_blank'),
|
||||
'video' => array('controls' => 'true'),
|
||||
);
|
||||
|
||||
/**
|
||||
* List of filters to apply.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $filters = array(
|
||||
'filterAllowedAttribute',
|
||||
'filterIntegerAttribute',
|
||||
'rewriteAbsoluteUrl',
|
||||
'filterIframeAttribute',
|
||||
'filterBlacklistResourceAttribute',
|
||||
'filterProtocolUrlAttribute',
|
||||
'rewriteImageProxyUrl',
|
||||
'secureIframeSrc',
|
||||
'removeYouTubeAutoplay',
|
||||
);
|
||||
|
||||
/**
|
||||
* Add attributes to specified tags.
|
||||
*
|
||||
* @var \PicoFeed\Client\Url
|
||||
*/
|
||||
private $website;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \PicoFeed\Client\Url $website Website url instance
|
||||
*/
|
||||
public function __construct(Url $website)
|
||||
{
|
||||
$this->website = $website;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters to the attributes list.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param array $attributes Attributes dictionary
|
||||
*
|
||||
* @return array Filtered attributes
|
||||
*/
|
||||
public function filter($tag, array $attributes)
|
||||
{
|
||||
foreach ($attributes as $attribute => &$value) {
|
||||
foreach ($this->filters as $filter) {
|
||||
if (!$this->$filter($tag, $attribute, $value)) {
|
||||
unset($attributes[$attribute]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the value is allowed (remove not allowed attributes).
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param string $attribute Attribute name
|
||||
* @param string $value Attribute value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function filterAllowedAttribute($tag, $attribute, $value)
|
||||
{
|
||||
return isset($this->attribute_whitelist[$tag]) && in_array($attribute, $this->attribute_whitelist[$tag]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the value is not integer (remove attributes that should have an integer value).
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param string $attribute Attribute name
|
||||
* @param string $value Attribute value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function filterIntegerAttribute($tag, $attribute, $value)
|
||||
{
|
||||
if (in_array($attribute, $this->integer_attributes)) {
|
||||
return ctype_digit($value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the iframe source is allowed (remove not allowed iframe).
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param string $attribute Attribute name
|
||||
* @param string $value Attribute value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function filterIframeAttribute($tag, $attribute, $value)
|
||||
{
|
||||
if ($tag === 'iframe' && $attribute === 'src') {
|
||||
foreach ($this->iframe_whitelist as $url) {
|
||||
if (strpos($value, $url) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the resource is not blacklisted (remove blacklisted resource attributes).
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param string $attribute Attribute name
|
||||
* @param string $value Attribute value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function filterBlacklistResourceAttribute($tag, $attribute, $value)
|
||||
{
|
||||
if ($this->isResource($attribute) && $this->isBlacklistedMedia($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert all relative links to absolute url.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param string $attribute Attribute name
|
||||
* @param string $value Attribute value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function rewriteAbsoluteUrl($tag, $attribute, &$value)
|
||||
{
|
||||
if ($this->isResource($attribute)) {
|
||||
$value = Url::resolve($value, $this->website);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns iframes' src attribute from http to https to prevent
|
||||
* mixed active content.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param array $attribute Atttributes name
|
||||
* @param string $value Attribute value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function secureIframeSrc($tag, $attribute, &$value)
|
||||
{
|
||||
if ($tag === 'iframe' && $attribute === 'src' && strpos($value, 'http://') === 0) {
|
||||
$value = substr_replace($value, 's', 4, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes YouTube autoplay from iframes.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param array $attribute Atttributes name
|
||||
* @param string $value Attribute value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function removeYouTubeAutoplay($tag, $attribute, &$value)
|
||||
{
|
||||
$regex = '%^(https://(?:www\.)?youtube.com/.*\?.*autoplay=)(1)(.*)%i';
|
||||
if ($tag === 'iframe' && $attribute === 'src' && preg_match($regex, $value)) {
|
||||
$value = preg_replace($regex, '${1}0$3', $value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite image url to use with a proxy.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param string $attribute Attribute name
|
||||
* @param string $value Attribute value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function rewriteImageProxyUrl($tag, $attribute, &$value)
|
||||
{
|
||||
if ($tag === 'img' && $attribute === 'src'
|
||||
&& !($this->image_proxy_limit_protocol !== '' && stripos($value, $this->image_proxy_limit_protocol.':') !== 0)) {
|
||||
if ($this->image_proxy_url) {
|
||||
$value = sprintf($this->image_proxy_url, rawurlencode($value));
|
||||
} elseif (is_callable($this->image_proxy_callback)) {
|
||||
$value = call_user_func($this->image_proxy_callback, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the scheme is authorized.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param string $attribute Attribute name
|
||||
* @param string $value Attribute value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function filterProtocolUrlAttribute($tag, $attribute, $value)
|
||||
{
|
||||
if ($this->isResource($attribute) && !$this->isAllowedProtocol($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically add/override some attributes for specific tags.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param array $attributes Attributes list
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function addAttributes($tag, array $attributes)
|
||||
{
|
||||
if (isset($this->add_attributes[$tag])) {
|
||||
$attributes += $this->add_attributes[$tag];
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if all required attributes are present.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param array $attributes Attributes list
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasRequiredAttributes($tag, array $attributes)
|
||||
{
|
||||
if (isset($this->required_attributes[$tag])) {
|
||||
foreach ($this->required_attributes[$tag] as $attribute) {
|
||||
if (!isset($attributes[$attribute])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an attribute name is an external resource.
|
||||
*
|
||||
* @param string $attribute Attribute name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isResource($attribute)
|
||||
{
|
||||
return in_array($attribute, $this->media_attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the protocol is allowed or not.
|
||||
*
|
||||
* @param string $value Attribute value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAllowedProtocol($value)
|
||||
{
|
||||
foreach ($this->scheme_whitelist as $protocol) {
|
||||
if (strpos($value, $protocol) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if an url is blacklisted.
|
||||
*
|
||||
* @param string $resource Attribute value (URL)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isBlacklistedMedia($resource)
|
||||
{
|
||||
foreach ($this->media_blacklist as $name) {
|
||||
if (strpos($resource, $name) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the attribute list to html.
|
||||
*
|
||||
* @param array $attributes Attributes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHtml(array $attributes)
|
||||
{
|
||||
$html = array();
|
||||
|
||||
foreach ($attributes as $attribute => $value) {
|
||||
$html[] = sprintf('%s="%s"', $attribute, Filter::escape($value));
|
||||
}
|
||||
|
||||
return implode(' ', $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whitelisted tags and attributes for each tag.
|
||||
*
|
||||
* @param array $values List of tags: ['video' => ['src', 'cover'], 'img' => ['src']]
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
public function setWhitelistedAttributes(array $values)
|
||||
{
|
||||
$this->attribute_whitelist = $values ?: $this->attribute_whitelist;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set scheme whitelist.
|
||||
*
|
||||
* @param array $values List of scheme: ['http://', 'ftp://']
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
public function setSchemeWhitelist(array $values)
|
||||
{
|
||||
$this->scheme_whitelist = $values ?: $this->scheme_whitelist;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set media attributes (used to load external resources).
|
||||
*
|
||||
* @param array $values List of values: ['src', 'href']
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
public function setMediaAttributes(array $values)
|
||||
{
|
||||
$this->media_attributes = $values ?: $this->media_attributes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set blacklisted external resources.
|
||||
*
|
||||
* @param array $values List of tags: ['http://google.com/', '...']
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
public function setMediaBlacklist(array $values)
|
||||
{
|
||||
$this->media_blacklist = $values ?: $this->media_blacklist;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mandatory attributes for whitelisted tags.
|
||||
*
|
||||
* @param array $values List of tags: ['img' => 'src']
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
public function setRequiredAttributes(array $values)
|
||||
{
|
||||
$this->required_attributes = $values ?: $this->required_attributes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attributes to automatically to specific tags.
|
||||
*
|
||||
* @param array $values List of tags: ['a' => 'target="_blank"']
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
public function setAttributeOverrides(array $values)
|
||||
{
|
||||
$this->add_attributes = $values ?: $this->add_attributes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attributes that must be an integer.
|
||||
*
|
||||
* @param array $values List of tags: ['width', 'height']
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
public function setIntegerAttributes(array $values)
|
||||
{
|
||||
$this->integer_attributes = $values ?: $this->integer_attributes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set allowed iframe resources.
|
||||
*
|
||||
* @param array $values List of tags: ['http://www.youtube.com']
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
public function setIframeWhitelist(array $values)
|
||||
{
|
||||
$this->iframe_whitelist = $values ?: $this->iframe_whitelist;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image proxy URL.
|
||||
*
|
||||
* The original image url will be urlencoded
|
||||
*
|
||||
* @param string $url Proxy URL
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
public function setImageProxyUrl($url)
|
||||
{
|
||||
$this->image_proxy_url = $url ?: $this->image_proxy_url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image proxy callback.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
public function setImageProxyCallback($callback)
|
||||
{
|
||||
$this->image_proxy_callback = $callback ?: $this->image_proxy_callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image proxy protocol restriction.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
public function setImageProxyProtocol($value)
|
||||
{
|
||||
$this->image_proxy_limit_protocol = $value ?: $this->image_proxy_limit_protocol;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Filter;
|
||||
|
||||
/**
|
||||
* Filter class.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Filter
|
||||
{
|
||||
/**
|
||||
* Get the Html filter instance.
|
||||
*
|
||||
* @static
|
||||
* @param string $html HTML content
|
||||
* @param string $website Site URL (used to build absolute URL)
|
||||
* @return Html
|
||||
*/
|
||||
public static function html($html, $website)
|
||||
{
|
||||
$filter = new Html($html, $website);
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML content.
|
||||
*
|
||||
* @static
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
public static function escape($content)
|
||||
{
|
||||
return htmlspecialchars($content, ENT_QUOTES, 'UTF-8', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove HTML tags.
|
||||
*
|
||||
* @param string $data Input data
|
||||
* @return string
|
||||
*/
|
||||
public function removeHTMLTags($data)
|
||||
{
|
||||
return preg_replace('~<(?:!DOCTYPE|/?(?:html|head|body))[^>]*>\s*~i', '', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the XML tag from a document.
|
||||
*
|
||||
* @static
|
||||
* @param string $data Input data
|
||||
* @return string
|
||||
*/
|
||||
public static function stripXmlTag($data)
|
||||
{
|
||||
if (strpos($data, '<?xml') !== false) {
|
||||
$data = ltrim(substr($data, strpos($data, '?>') + 2));
|
||||
}
|
||||
|
||||
do {
|
||||
$pos = strpos($data, '<?xml-stylesheet ');
|
||||
|
||||
if ($pos !== false) {
|
||||
$data = ltrim(substr($data, strpos($data, '?>') + 2));
|
||||
}
|
||||
} while ($pos !== false && $pos < 200);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip head tag from the HTML content.
|
||||
*
|
||||
* @static
|
||||
* @param string $data Input data
|
||||
* @return string
|
||||
*/
|
||||
public static function stripHeadTags($data)
|
||||
{
|
||||
return preg_replace('@<head[^>]*?>.*?</head>@siu', '', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim whitespace from the begining, the end and inside a string and don't break utf-8 string.
|
||||
*
|
||||
* @static
|
||||
* @param string $value Raw data
|
||||
* @return string Normalized data
|
||||
*/
|
||||
public static function stripWhiteSpace($value)
|
||||
{
|
||||
$value = str_replace("\r", ' ', $value);
|
||||
$value = str_replace("\t", ' ', $value);
|
||||
$value = str_replace("\n", ' ', $value);
|
||||
// $value = preg_replace('/\s+/', ' ', $value); <= break utf-8
|
||||
return trim($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes before XML parsing.
|
||||
*
|
||||
* @static
|
||||
* @param string $data Raw data
|
||||
* @return string Normalized data
|
||||
*/
|
||||
public static function normalizeData($data)
|
||||
{
|
||||
$entities = array(
|
||||
'/(&#)(\d+);/m', // decimal encoded
|
||||
'/(&#x)([a-f0-9]+);/mi', // hex encoded
|
||||
);
|
||||
|
||||
// strip invalid XML 1.0 characters which are encoded as entities
|
||||
$data = preg_replace_callback($entities, function ($matches) {
|
||||
$code_point = $matches[2];
|
||||
|
||||
// convert hex entity to decimal
|
||||
if (strtolower($matches[1]) === '&#x') {
|
||||
$code_point = hexdec($code_point);
|
||||
}
|
||||
|
||||
$code_point = (int) $code_point;
|
||||
|
||||
// replace invalid characters
|
||||
if ($code_point < 9
|
||||
|| ($code_point > 10 && $code_point < 13)
|
||||
|| ($code_point > 13 && $code_point < 32)
|
||||
|| ($code_point > 55295 && $code_point < 57344)
|
||||
|| ($code_point > 65533 && $code_point < 65536)
|
||||
|| $code_point > 1114111
|
||||
) {
|
||||
return '';
|
||||
};
|
||||
|
||||
return $matches[0];
|
||||
}, $data);
|
||||
|
||||
// strip every utf-8 character than isn't in the range of valid XML 1.0 characters
|
||||
return (string) preg_replace('/[^\x{0009}\x{000A}\x{000D}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]/u', '', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Filter;
|
||||
|
||||
use PicoFeed\Config\Config;
|
||||
use PicoFeed\Client\Url;
|
||||
use PicoFeed\Scraper\RuleLoader;
|
||||
use PicoFeed\Parser\XmlParser;
|
||||
|
||||
/**
|
||||
* HTML Filter class.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Html
|
||||
{
|
||||
/**
|
||||
* Config object.
|
||||
*
|
||||
* @var \PicoFeed\Config\Config
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Unfiltered XML data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $input = '';
|
||||
|
||||
/**
|
||||
* Filtered XML data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $output = '';
|
||||
|
||||
/**
|
||||
* List of empty tags.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $empty_tags = array();
|
||||
|
||||
/**
|
||||
* Empty flag.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $empty = true;
|
||||
|
||||
/**
|
||||
* Tag instance.
|
||||
*
|
||||
* @var \PicoFeed\Filter\Tag
|
||||
*/
|
||||
public $tag = '';
|
||||
|
||||
/**
|
||||
* Attribute instance.
|
||||
*
|
||||
* @var \PicoFeed\Filter\Attribute
|
||||
*/
|
||||
public $attribute = '';
|
||||
|
||||
/**
|
||||
* The website to filter.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $website;
|
||||
|
||||
/**
|
||||
* Initialize the filter, all inputs data must be encoded in UTF-8 before.
|
||||
*
|
||||
* @param string $html HTML content
|
||||
* @param string $website Site URL (used to build absolute URL)
|
||||
*/
|
||||
public function __construct($html, $website)
|
||||
{
|
||||
$this->config = new Config();
|
||||
$this->input = XmlParser::htmlToXml($html);
|
||||
$this->output = '';
|
||||
$this->tag = new Tag($this->config);
|
||||
$this->website = $website;
|
||||
$this->attribute = new Attribute(new Url($website));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set config object.
|
||||
*
|
||||
* @param \PicoFeed\Config\Config $config Config instance
|
||||
* @return \PicoFeed\Filter\Html
|
||||
*/
|
||||
public function setConfig($config)
|
||||
{
|
||||
$this->config = $config;
|
||||
|
||||
if ($this->config !== null) {
|
||||
$this->attribute->setImageProxyCallback($this->config->getFilterImageProxyCallback());
|
||||
$this->attribute->setImageProxyUrl($this->config->getFilterImageProxyUrl());
|
||||
$this->attribute->setImageProxyProtocol($this->config->getFilterImageProxyProtocol());
|
||||
$this->attribute->setIframeWhitelist($this->config->getFilterIframeWhitelist(array()));
|
||||
$this->attribute->setIntegerAttributes($this->config->getFilterIntegerAttributes(array()));
|
||||
$this->attribute->setAttributeOverrides($this->config->getFilterAttributeOverrides(array()));
|
||||
$this->attribute->setRequiredAttributes($this->config->getFilterRequiredAttributes(array()));
|
||||
$this->attribute->setMediaBlacklist($this->config->getFilterMediaBlacklist(array()));
|
||||
$this->attribute->setMediaAttributes($this->config->getFilterMediaAttributes(array()));
|
||||
$this->attribute->setSchemeWhitelist($this->config->getFilterSchemeWhitelist(array()));
|
||||
$this->attribute->setWhitelistedAttributes($this->config->getFilterWhitelistedTags(array()));
|
||||
$this->tag->setWhitelistedTags(array_keys($this->config->getFilterWhitelistedTags(array())));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run tags/attributes filtering.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$this->preFilter();
|
||||
|
||||
$parser = xml_parser_create();
|
||||
|
||||
xml_set_object($parser, $this);
|
||||
xml_set_element_handler($parser, 'startTag', 'endTag');
|
||||
xml_set_character_data_handler($parser, 'dataTag');
|
||||
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
|
||||
xml_parse($parser, $this->input, true);
|
||||
xml_parser_free($parser);
|
||||
|
||||
$this->postFilter();
|
||||
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before XML parsing.
|
||||
*/
|
||||
public function preFilter()
|
||||
{
|
||||
$this->input = $this->tag->removeBlacklistedTags($this->input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after XML parsing.
|
||||
*/
|
||||
public function postFilter()
|
||||
{
|
||||
$this->output = $this->tag->removeEmptyTags($this->output);
|
||||
$this->output = $this->filterRules($this->output);
|
||||
$this->output = $this->tag->removeMultipleBreakTags($this->output);
|
||||
$this->output = trim($this->output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after XML parsing.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
public function filterRules($content)
|
||||
{
|
||||
// the constructor should require a config, then this if can be removed
|
||||
if ($this->config === null) {
|
||||
$config = new Config();
|
||||
} else {
|
||||
$config = $this->config;
|
||||
}
|
||||
|
||||
$loader = new RuleLoader($config);
|
||||
$rules = $loader->getRules($this->website);
|
||||
|
||||
$url = new Url($this->website);
|
||||
$sub_url = $url->getFullPath();
|
||||
|
||||
if (isset($rules['filter'])) {
|
||||
foreach ($rules['filter'] as $pattern => $rule) {
|
||||
if (preg_match($pattern, $sub_url)) {
|
||||
foreach ($rule as $search => $replace) {
|
||||
$content = preg_replace($search, $replace, $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse opening tag.
|
||||
*
|
||||
* @param resource $parser XML parser
|
||||
* @param string $tag Tag name
|
||||
* @param array $attributes Tag attributes
|
||||
*/
|
||||
public function startTag($parser, $tag, array $attributes)
|
||||
{
|
||||
$this->empty = true;
|
||||
|
||||
if ($this->tag->isAllowed($tag, $attributes)) {
|
||||
$attributes = $this->attribute->filter($tag, $attributes);
|
||||
|
||||
if ($this->attribute->hasRequiredAttributes($tag, $attributes)) {
|
||||
$attributes = $this->attribute->addAttributes($tag, $attributes);
|
||||
|
||||
$this->output .= $this->tag->openHtmlTag($tag, $this->attribute->toHtml($attributes));
|
||||
$this->empty = false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->empty_tags[] = $this->empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse closing tag.
|
||||
*
|
||||
* @param resource $parser XML parser
|
||||
* @param string $tag Tag name
|
||||
*/
|
||||
public function endTag($parser, $tag)
|
||||
{
|
||||
if (!array_pop($this->empty_tags) && $this->tag->isAllowedTag($tag)) {
|
||||
$this->output .= $this->tag->closeHtmlTag($tag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tag content.
|
||||
*
|
||||
* @param resource $parser XML parser
|
||||
* @param string $content Tag content
|
||||
*/
|
||||
public function dataTag($parser, $content)
|
||||
{
|
||||
// Replace with normal space
|
||||
$content = str_replace("\xc2\xa0", ' ', $content);
|
||||
$this->output .= Filter::escape($content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Filter;
|
||||
|
||||
use DOMXPath;
|
||||
use PicoFeed\Base;
|
||||
use PicoFeed\Parser\XmlParser;
|
||||
|
||||
/**
|
||||
* Tag Filter class.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Tag extends Base
|
||||
{
|
||||
/**
|
||||
* Tags blacklist (Xpath expressions).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $tag_blacklist = array(
|
||||
'//script',
|
||||
'//style',
|
||||
);
|
||||
|
||||
/**
|
||||
* Tags whitelist.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $tag_whitelist = array(
|
||||
'audio',
|
||||
'video',
|
||||
'source',
|
||||
'dt',
|
||||
'dd',
|
||||
'dl',
|
||||
'table',
|
||||
'caption',
|
||||
'tr',
|
||||
'th',
|
||||
'td',
|
||||
'tbody',
|
||||
'thead',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'strong',
|
||||
'em',
|
||||
'code',
|
||||
'pre',
|
||||
'blockquote',
|
||||
'p',
|
||||
'ul',
|
||||
'li',
|
||||
'ol',
|
||||
'br',
|
||||
'del',
|
||||
'a',
|
||||
'img',
|
||||
'figure',
|
||||
'figcaption',
|
||||
'cite',
|
||||
'time',
|
||||
'abbr',
|
||||
'iframe',
|
||||
'q',
|
||||
'sup',
|
||||
'sub',
|
||||
);
|
||||
|
||||
/**
|
||||
* Check if the tag is allowed and is not a pixel tracker.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param array $attributes Attributes dictionary
|
||||
* @return bool
|
||||
*/
|
||||
public function isAllowed($tag, array $attributes)
|
||||
{
|
||||
return $this->isAllowedTag($tag) && !$this->isPixelTracker($tag, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HTML opening tag.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param string $attributes Attributes converted in html
|
||||
* @return string
|
||||
*/
|
||||
public function openHtmlTag($tag, $attributes = '')
|
||||
{
|
||||
return '<'.$tag.(empty($attributes) ? '' : ' '.$attributes).($this->isSelfClosingTag($tag) ? '/>' : '>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HTML closing tag.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @return string
|
||||
*/
|
||||
public function closeHtmlTag($tag)
|
||||
{
|
||||
return $this->isSelfClosingTag($tag) ? '' : '</'.$tag.'>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true is the tag is self-closing.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @return bool
|
||||
*/
|
||||
public function isSelfClosingTag($tag)
|
||||
{
|
||||
return $tag === 'br' || $tag === 'img';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tag is on the whitelist.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @return bool
|
||||
*/
|
||||
public function isAllowedTag($tag)
|
||||
{
|
||||
return in_array($tag, array_merge(
|
||||
$this->tag_whitelist,
|
||||
array_keys($this->config->getFilterWhitelistedTags(array()))
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if an image tag is a pixel tracker.
|
||||
*
|
||||
* @param string $tag Tag name
|
||||
* @param array $attributes Tag attributes
|
||||
* @return bool
|
||||
*/
|
||||
public function isPixelTracker($tag, array $attributes)
|
||||
{
|
||||
return $tag === 'img' &&
|
||||
isset($attributes['height']) && isset($attributes['width']) &&
|
||||
$attributes['height'] == 1 && $attributes['width'] == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove script tags.
|
||||
*
|
||||
* @param string $data Input data
|
||||
* @return string
|
||||
*/
|
||||
public function removeBlacklistedTags($data)
|
||||
{
|
||||
$dom = XmlParser::getDomDocument($data);
|
||||
|
||||
if ($dom === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$xpath = new DOMXpath($dom);
|
||||
|
||||
$nodes = $xpath->query(implode(' | ', $this->tag_blacklist));
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$node->parentNode->removeChild($node);
|
||||
}
|
||||
|
||||
return $dom->saveXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove empty tags.
|
||||
*
|
||||
* @param string $data Input data
|
||||
* @return string
|
||||
*/
|
||||
public function removeEmptyTags($data)
|
||||
{
|
||||
return preg_replace('/<([^<\/>]*)>([\s]*?|(?R))<\/\1>/imsU', '', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace <br/><br/> by only one.
|
||||
*
|
||||
* @param string $data Input data
|
||||
* @return string
|
||||
*/
|
||||
public function removeMultipleBreakTags($data)
|
||||
{
|
||||
return preg_replace("/(<br\s*\/?>\s*)+/", '<br/>', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whitelisted tags adn attributes for each tag.
|
||||
*
|
||||
* @param array $values List of tags: ['video' => ['src', 'cover'], 'img' => ['src']]
|
||||
* @return Tag
|
||||
*/
|
||||
public function setWhitelistedTags(array $values)
|
||||
{
|
||||
$this->tag_whitelist = $values ?: $this->tag_whitelist;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Generator;
|
||||
|
||||
use PicoFeed\Parser\Item;
|
||||
|
||||
/**
|
||||
* Content Generator Interface
|
||||
*
|
||||
* @package PicoFeed\Generator
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface ContentGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* Execute Content Generator
|
||||
*
|
||||
* @access public
|
||||
* @param Item $item
|
||||
* @return boolean
|
||||
*/
|
||||
public function execute(Item $item);
|
||||
}
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Generator;
|
||||
|
||||
use PicoFeed\Base;
|
||||
use PicoFeed\Parser\Item;
|
||||
|
||||
/**
|
||||
* File Content Generator
|
||||
*
|
||||
* @package PicoFeed\Generator
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class FileContentGenerator extends Base implements ContentGeneratorInterface
|
||||
{
|
||||
private $extensions = array('pdf');
|
||||
|
||||
/**
|
||||
* Execute Content Generator
|
||||
*
|
||||
* @access public
|
||||
* @param Item $item
|
||||
* @return boolean
|
||||
*/
|
||||
public function execute(Item $item)
|
||||
{
|
||||
foreach ($this->extensions as $extension) {
|
||||
if (substr($item->getUrl(), - strlen($extension)) === $extension) {
|
||||
$item->setContent('<a href="'.$item->getUrl().'" target="_blank">'.$item->getUrl().'</a>');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Generator;
|
||||
|
||||
use PicoFeed\Base;
|
||||
use PicoFeed\Parser\Item;
|
||||
|
||||
/**
|
||||
* Youtube Content Generator
|
||||
*
|
||||
* @package PicoFeed\Generator
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class YoutubeContentGenerator extends Base implements ContentGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* Execute Content Generator
|
||||
*
|
||||
* @access public
|
||||
* @param Item $item
|
||||
* @return boolean
|
||||
*/
|
||||
public function execute(Item $item)
|
||||
{
|
||||
if ($item->hasNamespace('yt')) {
|
||||
return $this->generateHtmlFromXml($item);
|
||||
}
|
||||
|
||||
return $this->generateHtmlFromUrl($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HTML
|
||||
*
|
||||
* @access public
|
||||
* @param Item $item
|
||||
* @return boolean
|
||||
*/
|
||||
private function generateHtmlFromXml(Item $item)
|
||||
{
|
||||
$videoId = $item->getTag('yt:videoId');
|
||||
|
||||
if (! empty($videoId)) {
|
||||
$item->setContent('<iframe width="560" height="315" src="//www.youtube.com/embed/'.$videoId[0].'" frameborder="0"></iframe>');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HTML from item URL
|
||||
*
|
||||
* @access public
|
||||
* @param Item $item
|
||||
* @return bool
|
||||
*/
|
||||
public function generateHtmlFromUrl(Item $item)
|
||||
{
|
||||
if (preg_match('/youtube\.com\/watch\?v=(.*)/', $item->getUrl(), $matches)) {
|
||||
$item->setContent('<iframe width="560" height="315" src="//www.youtube.com/embed/'.$matches[1].'" frameborder="0"></iframe>');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Logging;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
|
||||
/**
|
||||
* Logging class.
|
||||
*
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Logger
|
||||
{
|
||||
/**
|
||||
* List of messages.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $messages = array();
|
||||
|
||||
/**
|
||||
* Default timezone.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $timezone = 'UTC';
|
||||
|
||||
/**
|
||||
* Enable or disable logging.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $enable = false;
|
||||
|
||||
/**
|
||||
* Enable logging.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function enable()
|
||||
{
|
||||
self::$enable = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new message.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param string $message Message
|
||||
*/
|
||||
public static function setMessage($message)
|
||||
{
|
||||
if (self::$enable) {
|
||||
$date = new DateTime('now', new DateTimeZone(self::$timezone));
|
||||
self::$messages[] = '['.$date->format('Y-m-d H:i:s').'] '.$message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all logged messages.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getMessages()
|
||||
{
|
||||
return self::$messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all logged messages.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function deleteMessages()
|
||||
{
|
||||
self::$messages = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a different timezone.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @see http://php.net/manual/en/timezones.php
|
||||
*
|
||||
* @param string $timezone Timezone
|
||||
*/
|
||||
public static function setTimeZone($timezone)
|
||||
{
|
||||
self::$timezone = $timezone ?: self::$timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all messages serialized into a string.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function toString()
|
||||
{
|
||||
return implode(PHP_EOL, self::$messages).PHP_EOL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Parser;
|
||||
|
||||
use SimpleXMLElement;
|
||||
use PicoFeed\Filter\Filter;
|
||||
use PicoFeed\Client\Url;
|
||||
|
||||
/**
|
||||
* Atom parser.
|
||||
*
|
||||
* @package PicoFeed\Parser
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Atom extends Parser
|
||||
{
|
||||
/**
|
||||
* Supported namespaces.
|
||||
*/
|
||||
protected $namespaces = array(
|
||||
'atom' => 'http://www.w3.org/2005/Atom',
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the path to the items XML tree.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @return SimpleXMLElement[]
|
||||
*/
|
||||
public function getItemsTree(SimpleXMLElement $xml)
|
||||
{
|
||||
return XmlParser::getXPathResult($xml, 'atom:entry', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($xml, 'entry');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the feed url.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedUrl(SimpleXMLElement $xml, Feed $feed)
|
||||
{
|
||||
$feed->setFeedUrl($this->getUrl($xml, 'self'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the site url.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findSiteUrl(SimpleXMLElement $xml, Feed $feed)
|
||||
{
|
||||
$feed->setSiteUrl($this->getUrl($xml, 'alternate', true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the feed description.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedDescription(SimpleXMLElement $xml, Feed $feed)
|
||||
{
|
||||
$description = XmlParser::getXPathResult($xml, 'atom:subtitle', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($xml, 'subtitle');
|
||||
|
||||
$feed->setDescription(XmlParser::getValue($description));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the feed logo url.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedLogo(SimpleXMLElement $xml, Feed $feed)
|
||||
{
|
||||
$logo = XmlParser::getXPathResult($xml, 'atom:logo', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($xml, 'logo');
|
||||
|
||||
$feed->setLogo(XmlParser::getValue($logo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the feed icon.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedIcon(SimpleXMLElement $xml, Feed $feed)
|
||||
{
|
||||
$icon = XmlParser::getXPathResult($xml, 'atom:icon', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($xml, 'icon');
|
||||
|
||||
$feed->setIcon(XmlParser::getValue($icon));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the feed title.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedTitle(SimpleXMLElement $xml, Feed $feed)
|
||||
{
|
||||
$title = XmlParser::getXPathResult($xml, 'atom:title', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($xml, 'title');
|
||||
|
||||
$feed->setTitle(Filter::stripWhiteSpace(XmlParser::getValue($title)) ?: $feed->getSiteUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the feed language.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedLanguage(SimpleXMLElement $xml, Feed $feed)
|
||||
{
|
||||
$language = XmlParser::getXPathResult($xml, '*[not(self::atom:entry)]/@xml:lang', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($xml, '@xml:lang');
|
||||
|
||||
$feed->setLanguage(XmlParser::getValue($language));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the feed id.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedId(SimpleXMLElement $xml, Feed $feed)
|
||||
{
|
||||
$id = XmlParser::getXPathResult($xml, 'atom:id', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($xml, 'id');
|
||||
|
||||
$feed->setId(XmlParser::getValue($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the feed date.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedDate(SimpleXMLElement $xml, Feed $feed)
|
||||
{
|
||||
$updated = XmlParser::getXPathResult($xml, 'atom:updated', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($xml, 'updated');
|
||||
|
||||
$feed->setDate($this->getDateParser()->getDateTime(XmlParser::getValue($updated)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the item published date.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findItemPublishedDate(SimpleXMLElement $entry, Item $item, Feed $feed)
|
||||
{
|
||||
$date = XmlParser::getXPathResult($entry, 'atom:published', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($entry, 'published');
|
||||
|
||||
$item->setPublishedDate(!empty($date) ? $this->getDateParser()->getDateTime((string) current($date)) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the item updated date.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findItemUpdatedDate(SimpleXMLElement $entry, Item $item, Feed $feed)
|
||||
{
|
||||
$date = XmlParser::getXPathResult($entry, 'atom:updated', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($entry, 'updated');
|
||||
|
||||
$item->setUpdatedDate(!empty($date) ? $this->getDateParser()->getDateTime((string) current($date)) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the item title.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
*/
|
||||
public function findItemTitle(SimpleXMLElement $entry, Item $item)
|
||||
{
|
||||
$title = XmlParser::getXPathResult($entry, 'atom:title', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($entry, 'title');
|
||||
|
||||
$item->setTitle(Filter::stripWhiteSpace(XmlParser::getValue($title)) ?: $item->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the item author.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param \PicoFeed\Parser\Item $item Item object
|
||||
*/
|
||||
public function findItemAuthor(SimpleXMLElement $xml, SimpleXMLElement $entry, Item $item)
|
||||
{
|
||||
$author = XmlParser::getXPathResult($entry, 'atom:author/atom:name', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($entry, 'author/name')
|
||||
?: XmlParser::getXPathResult($xml, 'atom:author/atom:name', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($xml, 'author/name');
|
||||
|
||||
$item->setAuthor(XmlParser::getValue($author));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the item content.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param \PicoFeed\Parser\Item $item Item object
|
||||
*/
|
||||
public function findItemContent(SimpleXMLElement $entry, Item $item)
|
||||
{
|
||||
$item->setContent($this->getContent($entry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the item URL.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param \PicoFeed\Parser\Item $item Item object
|
||||
*/
|
||||
public function findItemUrl(SimpleXMLElement $entry, Item $item)
|
||||
{
|
||||
$item->setUrl($this->getUrl($entry, 'alternate', true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Genereate the item id.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param \PicoFeed\Parser\Item $item Item object
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findItemId(SimpleXMLElement $entry, Item $item, Feed $feed)
|
||||
{
|
||||
$id = XmlParser::getXPathResult($entry, 'atom:id', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($entry, 'id');
|
||||
|
||||
if (!empty($id)) {
|
||||
$item->setId($this->generateId(XmlParser::getValue($id)));
|
||||
} else {
|
||||
$item->setId($this->generateId(
|
||||
$item->getTitle(), $item->getUrl(), $item->getContent()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the item enclosure.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param \PicoFeed\Parser\Item $item Item object
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findItemEnclosure(SimpleXMLElement $entry, Item $item, Feed $feed)
|
||||
{
|
||||
$enclosure = $this->findLink($entry, 'enclosure');
|
||||
|
||||
if ($enclosure) {
|
||||
$item->setEnclosureUrl(Url::resolve((string) $enclosure['href'], $feed->getSiteUrl()));
|
||||
$item->setEnclosureType((string) $enclosure['type']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the item language.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param \PicoFeed\Parser\Item $item Item object
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findItemLanguage(SimpleXMLElement $entry, Item $item, Feed $feed)
|
||||
{
|
||||
$language = XmlParser::getXPathResult($entry, './/@xml:lang');
|
||||
$item->setLanguage(XmlParser::getValue($language) ?: $feed->getLanguage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the item categories.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findItemCategories(SimpleXMLElement $entry, Item $item, Feed $feed)
|
||||
{
|
||||
$categories = XmlParser::getXPathResult($entry, 'atom:category/@term', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($entry, 'category/@term');
|
||||
$item->setCategoriesFromXml($categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL from a link tag.
|
||||
*
|
||||
* @param SimpleXMLElement $xml XML tag
|
||||
* @param string $rel Link relationship: alternate, enclosure, related, self, via
|
||||
* @return string
|
||||
*/
|
||||
private function getUrl(SimpleXMLElement $xml, $rel, $fallback = false)
|
||||
{
|
||||
$link = $this->findLink($xml, $rel);
|
||||
|
||||
if ($link) {
|
||||
return (string) $link['href'];
|
||||
}
|
||||
|
||||
if ($fallback) {
|
||||
$link = $this->findLink($xml, '');
|
||||
return $link ? (string) $link['href'] : '';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a link tag that match a relationship.
|
||||
*
|
||||
* @param SimpleXMLElement $xml XML tag
|
||||
* @param string $rel Link relationship: alternate, enclosure, related, self, via
|
||||
* @return SimpleXMLElement|null
|
||||
*/
|
||||
private function findLink(SimpleXMLElement $xml, $rel)
|
||||
{
|
||||
$links = XmlParser::getXPathResult($xml, 'atom:link', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($xml, 'link');
|
||||
|
||||
foreach ($links as $link) {
|
||||
if ($rel === (string) $link['rel']) {
|
||||
return $link;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entry content.
|
||||
*
|
||||
* @param SimpleXMLElement $entry XML Entry
|
||||
* @return string
|
||||
*/
|
||||
private function getContent(SimpleXMLElement $entry)
|
||||
{
|
||||
$content = current(
|
||||
XmlParser::getXPathResult($entry, 'atom:content', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($entry, 'content')
|
||||
);
|
||||
|
||||
if (!empty($content) && count($content->children())) {
|
||||
$xml_string = '';
|
||||
|
||||
foreach ($content->children() as $child) {
|
||||
$xml_string .= $child->asXML();
|
||||
}
|
||||
|
||||
return $xml_string;
|
||||
} elseif (trim((string) $content) !== '') {
|
||||
return (string) $content;
|
||||
}
|
||||
|
||||
$summary = XmlParser::getXPathResult($entry, 'atom:summary', $this->namespaces)
|
||||
?: XmlParser::getXPathResult($entry, 'summary');
|
||||
|
||||
return (string) current($summary);
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Parser;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use PicoFeed\Base;
|
||||
|
||||
/**
|
||||
* Date Parser.
|
||||
*
|
||||
* @package PicoFeed\Parser
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class DateParser extends Base
|
||||
{
|
||||
/**
|
||||
* Timezone used to parse feed dates.
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $timezone = 'UTC';
|
||||
|
||||
/**
|
||||
* Supported formats [ 'format' => length ].
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $formats = array(
|
||||
DATE_ATOM => null,
|
||||
DATE_RSS => null,
|
||||
DATE_COOKIE => null,
|
||||
DATE_ISO8601 => null,
|
||||
DATE_RFC822 => null,
|
||||
DATE_RFC850 => null,
|
||||
DATE_RFC1036 => null,
|
||||
DATE_RFC1123 => null,
|
||||
DATE_RFC2822 => null,
|
||||
DATE_RFC3339 => null,
|
||||
'l, d M Y H:i:s' => null,
|
||||
'D, d M Y H:i:s' => 25,
|
||||
'D, d M Y h:i:s' => 25,
|
||||
'D M d Y H:i:s' => 24,
|
||||
'j M Y H:i:s' => 20,
|
||||
'Y-m-d H:i:s' => 19,
|
||||
'Y-m-d\TH:i:s' => 19,
|
||||
'd/m/Y H:i:s' => 19,
|
||||
'D, d M Y' => 16,
|
||||
'Y-m-d' => 10,
|
||||
'd-m-Y' => 10,
|
||||
'm-d-Y' => 10,
|
||||
'd.m.Y' => 10,
|
||||
'm.d.Y' => 10,
|
||||
'd/m/Y' => 10,
|
||||
'm/d/Y' => 10,
|
||||
);
|
||||
|
||||
/**
|
||||
* Try to parse all date format for broken feeds.
|
||||
*
|
||||
* @param string $value Original date format
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
public function getDateTime($value)
|
||||
{
|
||||
$value = trim($value);
|
||||
|
||||
foreach ($this->formats as $format => $length) {
|
||||
$truncated_value = $value;
|
||||
if ($length !== null) {
|
||||
$truncated_value = substr($truncated_value, 0, $length);
|
||||
}
|
||||
|
||||
$date = $this->getValidDate($format, $truncated_value);
|
||||
if ($date !== false) {
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getCurrentDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a valid date from a given format.
|
||||
*
|
||||
* @param string $format Date format
|
||||
* @param string $value Original date value
|
||||
*
|
||||
* @return DateTime|bool
|
||||
*/
|
||||
public function getValidDate($format, $value)
|
||||
{
|
||||
$date = DateTime::createFromFormat($format, $value, $this->getTimeZone());
|
||||
|
||||
if ($date !== false) {
|
||||
$errors = DateTime::getLastErrors();
|
||||
|
||||
if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) {
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current datetime.
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
public function getCurrentDateTime()
|
||||
{
|
||||
return new DateTime('now', $this->getTimeZone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DateTimeZone instance
|
||||
*
|
||||
* @access public
|
||||
* @return DateTimeZone
|
||||
*/
|
||||
public function getTimeZone()
|
||||
{
|
||||
return new DateTimeZone($this->config->getTimezone() ?: $this->timezone);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Parser;
|
||||
|
||||
/**
|
||||
* Feed.
|
||||
*
|
||||
* @package PicoFeed\Parser
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Feed
|
||||
{
|
||||
/**
|
||||
* Feed items.
|
||||
*
|
||||
* @var Item[]
|
||||
*/
|
||||
public $items = array();
|
||||
|
||||
/**
|
||||
* Feed id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id = '';
|
||||
|
||||
/**
|
||||
* Feed title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* Feed description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $description = '';
|
||||
|
||||
/**
|
||||
* Feed url.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $feedUrl = '';
|
||||
|
||||
/**
|
||||
* Site url.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $siteUrl = '';
|
||||
|
||||
/**
|
||||
* Feed date.
|
||||
*
|
||||
* @var \DateTime
|
||||
*/
|
||||
public $date = null;
|
||||
|
||||
/**
|
||||
* Feed language.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $language = '';
|
||||
|
||||
/**
|
||||
* Feed logo URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $logo = '';
|
||||
|
||||
/**
|
||||
* Feed icon URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $icon = '';
|
||||
|
||||
/**
|
||||
* Return feed information.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$output = '';
|
||||
|
||||
foreach (array('id', 'title', 'feedUrl', 'siteUrl', 'language', 'description', 'logo') as $property) {
|
||||
$output .= 'Feed::'.$property.' = '.$this->$property.PHP_EOL;
|
||||
}
|
||||
|
||||
$output .= 'Feed::date = '.$this->date->format(DATE_RFC822).PHP_EOL;
|
||||
$output .= 'Feed::isRTL() = '.($this->isRTL() ? 'true' : 'false').PHP_EOL;
|
||||
$output .= 'Feed::items = '.count($this->items).' items'.PHP_EOL;
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
$output .= '----'.PHP_EOL;
|
||||
$output .= $item;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description.
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logo url.
|
||||
*/
|
||||
public function getLogo()
|
||||
{
|
||||
return $this->logo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the icon url.
|
||||
*/
|
||||
public function getIcon()
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get feed url.
|
||||
*/
|
||||
public function getFeedUrl()
|
||||
{
|
||||
return $this->feedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site url.
|
||||
*/
|
||||
public function getSiteUrl()
|
||||
{
|
||||
return $this->siteUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date.
|
||||
*/
|
||||
public function getDate()
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language.
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get feed items.
|
||||
*/
|
||||
public function getItems()
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the feed is "Right to Left".
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRTL()
|
||||
{
|
||||
return Parser::isLanguageRTL($this->language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set feed items.
|
||||
*
|
||||
* @param Item[] $items
|
||||
* @return Feed
|
||||
*/
|
||||
public function setItems(array $items)
|
||||
{
|
||||
$this->items = $items;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set feed id.
|
||||
*
|
||||
* @param string $id
|
||||
* @return Feed
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set feed title.
|
||||
*
|
||||
* @param string $title
|
||||
* @return Feed
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set feed description.
|
||||
*
|
||||
* @param string $description
|
||||
* @return Feed
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set feed url.
|
||||
*
|
||||
* @param string $feedUrl
|
||||
* @return Feed
|
||||
*/
|
||||
public function setFeedUrl($feedUrl)
|
||||
{
|
||||
$this->feedUrl = $feedUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set feed website url.
|
||||
*
|
||||
* @param string $siteUrl
|
||||
* @return Feed
|
||||
*/
|
||||
public function setSiteUrl($siteUrl)
|
||||
{
|
||||
$this->siteUrl = $siteUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set feed date.
|
||||
*
|
||||
* @param \DateTime $date
|
||||
* @return Feed
|
||||
*/
|
||||
public function setDate($date)
|
||||
{
|
||||
$this->date = $date;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set feed language.
|
||||
*
|
||||
* @param string $language
|
||||
* @return Feed
|
||||
*/
|
||||
public function setLanguage($language)
|
||||
{
|
||||
$this->language = $language;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set feed logo.
|
||||
*
|
||||
* @param string $logo
|
||||
* @return Feed
|
||||
*/
|
||||
public function setLogo($logo)
|
||||
{
|
||||
$this->logo = $logo;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set feed icon.
|
||||
*
|
||||
* @param string $icon
|
||||
* @return Feed
|
||||
*/
|
||||
public function setIcon($icon)
|
||||
{
|
||||
$this->icon = $icon;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Parser;
|
||||
|
||||
/**
|
||||
* Feed Item.
|
||||
*
|
||||
* @package PicoFeed\Parser
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Item
|
||||
{
|
||||
/**
|
||||
* List of known RTL languages.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $rtl = array(
|
||||
'ar', // Arabic (ar-**)
|
||||
'fa', // Farsi (fa-**)
|
||||
'ur', // Urdu (ur-**)
|
||||
'ps', // Pashtu (ps-**)
|
||||
'syr', // Syriac (syr-**)
|
||||
'dv', // Divehi (dv-**)
|
||||
'he', // Hebrew (he-**)
|
||||
'yi', // Yiddish (yi-**)
|
||||
);
|
||||
|
||||
/**
|
||||
* Item id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id = '';
|
||||
|
||||
/**
|
||||
* Item title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* Item url.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $url = '';
|
||||
|
||||
/**
|
||||
* Item author.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $author = '';
|
||||
|
||||
/**
|
||||
* Item date.
|
||||
*
|
||||
* @var \DateTime
|
||||
*/
|
||||
public $date = null;
|
||||
|
||||
/**
|
||||
* Item published date.
|
||||
*
|
||||
* @var \DateTime
|
||||
*/
|
||||
public $publishedDate = null;
|
||||
|
||||
/**
|
||||
* Item updated date.
|
||||
*
|
||||
* @var \DateTime
|
||||
*/
|
||||
public $updatedDate = null;
|
||||
|
||||
/**
|
||||
* Item content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content = '';
|
||||
|
||||
/**
|
||||
* Item enclosure url.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $enclosureUrl = '';
|
||||
|
||||
/**
|
||||
* Item enclusure type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $enclosureType = '';
|
||||
|
||||
/**
|
||||
* Item language.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $language = '';
|
||||
|
||||
/**
|
||||
* Item categories.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $categories = array();
|
||||
|
||||
/**
|
||||
* Raw XML.
|
||||
*
|
||||
* @var \SimpleXMLElement
|
||||
*/
|
||||
public $xml;
|
||||
|
||||
/**
|
||||
* List of namespaces.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $namespaces = array();
|
||||
|
||||
/**
|
||||
* Check if a XML namespace exists
|
||||
*
|
||||
* @access public
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public function hasNamespace($namespace)
|
||||
{
|
||||
return array_key_exists($namespace, $this->namespaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific XML tag or attribute value.
|
||||
*
|
||||
* @param string $tag Tag name (examples: guid, media:content)
|
||||
* @param string $attribute Tag attribute
|
||||
*
|
||||
* @return array|false Tag values or error
|
||||
*/
|
||||
public function getTag($tag, $attribute = '')
|
||||
{
|
||||
if ($attribute !== '') {
|
||||
$attribute = '/@'.$attribute;
|
||||
}
|
||||
|
||||
$query = './/'.$tag.$attribute;
|
||||
$elements = XmlParser::getXPathResult($this->xml, $query, $this->namespaces);
|
||||
|
||||
if ($elements === false) { // xPath error
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_map(function ($element) { return (string) $element;}, $elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return item information.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$output = '';
|
||||
|
||||
foreach (array('id', 'title', 'url', 'language', 'author', 'enclosureUrl', 'enclosureType') as $property) {
|
||||
$output .= 'Item::'.$property.' = '.$this->$property.PHP_EOL;
|
||||
}
|
||||
|
||||
$publishedDate = $this->publishedDate != null ? $this->publishedDate->format(DATE_RFC822) : null;
|
||||
$updatedDate = $this->updatedDate != null ? $this->updatedDate->format(DATE_RFC822) : null;
|
||||
|
||||
$categoryString = $this->categories != null ? implode(',', $this->categories) : null;
|
||||
|
||||
$output .= 'Item::date = '.$this->date->format(DATE_RFC822).PHP_EOL;
|
||||
$output .= 'Item::publishedDate = '.$publishedDate.PHP_EOL;
|
||||
$output .= 'Item::updatedDate = '.$updatedDate.PHP_EOL;
|
||||
$output .= 'Item::isRTL() = '.($this->isRTL() ? 'true' : 'false').PHP_EOL;
|
||||
$output .= 'Item::categories = ['.$categoryString.']'.PHP_EOL;
|
||||
$output .= 'Item::content = '.strlen($this->content).' bytes'.PHP_EOL;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set URL
|
||||
*
|
||||
* @access public
|
||||
* @param string $url
|
||||
* @return Item
|
||||
*/
|
||||
public function setUrl($url)
|
||||
{
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getDate()
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get published date.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getPublishedDate()
|
||||
{
|
||||
return $this->publishedDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get updated date.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getUpdatedDate()
|
||||
{
|
||||
return $this->updatedDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content
|
||||
*
|
||||
* @access public
|
||||
* @param string $value
|
||||
* @return Item
|
||||
*/
|
||||
public function setContent($value)
|
||||
{
|
||||
$this->content = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enclosure url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEnclosureUrl()
|
||||
{
|
||||
return $this->enclosureUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enclosure type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEnclosureType()
|
||||
{
|
||||
return $this->enclosureType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCategories()
|
||||
{
|
||||
return $this->categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get author.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthor()
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the item is "Right to Left".
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRTL()
|
||||
{
|
||||
return Parser::isLanguageRTL($this->language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item id.
|
||||
*
|
||||
* @param string $id
|
||||
* @return Item
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item title.
|
||||
*
|
||||
* @param string $title
|
||||
* @return Item
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set author.
|
||||
*
|
||||
* @param string $author
|
||||
* @return Item
|
||||
*/
|
||||
public function setAuthor($author)
|
||||
{
|
||||
$this->author = $author;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item date.
|
||||
*
|
||||
* @param \DateTime $date
|
||||
* @return Item
|
||||
*/
|
||||
public function setDate($date)
|
||||
{
|
||||
$this->date = $date;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item published date.
|
||||
*
|
||||
* @param \DateTime $publishedDate
|
||||
* @return Item
|
||||
*/
|
||||
public function setPublishedDate($publishedDate)
|
||||
{
|
||||
$this->publishedDate = $publishedDate;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item updated date.
|
||||
*
|
||||
* @param \DateTime $updatedDate
|
||||
* @return Item
|
||||
*/
|
||||
public function setUpdatedDate($updatedDate)
|
||||
{
|
||||
$this->updatedDate = $updatedDate;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set enclosure url.
|
||||
*
|
||||
* @param string $enclosureUrl
|
||||
* @return Item
|
||||
*/
|
||||
public function setEnclosureUrl($enclosureUrl)
|
||||
{
|
||||
$this->enclosureUrl = $enclosureUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set enclosure type.
|
||||
*
|
||||
* @param string $enclosureType
|
||||
* @return Item
|
||||
*/
|
||||
public function setEnclosureType($enclosureType)
|
||||
{
|
||||
$this->enclosureType = $enclosureType;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item language.
|
||||
*
|
||||
* @param string $language
|
||||
* @return Item
|
||||
*/
|
||||
public function setLanguage($language)
|
||||
{
|
||||
$this->language = $language;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item categories.
|
||||
*
|
||||
* @param array $categories
|
||||
* @return Item
|
||||
*/
|
||||
public function setCategories($categories)
|
||||
{
|
||||
$this->categories = $categories;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item categories from xml.
|
||||
*
|
||||
* @param |SimpleXMLElement[] $categories
|
||||
* @return Item
|
||||
*/
|
||||
public function setCategoriesFromXml($categories)
|
||||
{
|
||||
if ($categories !== false) {
|
||||
$this->setCategories(
|
||||
array_map(
|
||||
function ($element) {
|
||||
return trim((string) $element);
|
||||
},
|
||||
$categories
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set raw XML.
|
||||
*
|
||||
* @param \SimpleXMLElement $xml
|
||||
* @return Item
|
||||
*/
|
||||
public function setXml($xml)
|
||||
{
|
||||
$this->xml = $xml;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw XML.
|
||||
*
|
||||
* @return \SimpleXMLElement
|
||||
*/
|
||||
public function getXml()
|
||||
{
|
||||
return $this->xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set XML namespaces.
|
||||
*
|
||||
* @param array $namespaces
|
||||
* @return Item
|
||||
*/
|
||||
public function setNamespaces($namespaces)
|
||||
{
|
||||
$this->namespaces = $namespaces;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get XML namespaces.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getNamespaces()
|
||||
{
|
||||
return $this->namespaces;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Parser;
|
||||
|
||||
/**
|
||||
* MalformedXmlException Exception.
|
||||
*
|
||||
* @package PicoFeed\Parser
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class MalformedXmlException extends ParserException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Parser;
|
||||
|
||||
use PicoFeed\Processor\ContentFilterProcessor;
|
||||
use PicoFeed\Processor\ContentGeneratorProcessor;
|
||||
use PicoFeed\Processor\ItemPostProcessor;
|
||||
use PicoFeed\Processor\ScraperProcessor;
|
||||
use SimpleXMLElement;
|
||||
use PicoFeed\Client\Url;
|
||||
use PicoFeed\Encoding\Encoding;
|
||||
use PicoFeed\Filter\Filter;
|
||||
use PicoFeed\Logging\Logger;
|
||||
|
||||
/**
|
||||
* Base parser class.
|
||||
*
|
||||
* @package PicoFeed\Parser
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class Parser implements ParserInterface
|
||||
{
|
||||
/**
|
||||
* Config object.
|
||||
*
|
||||
* @var \PicoFeed\Config\Config
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* DateParser object.
|
||||
*
|
||||
* @var \PicoFeed\Parser\DateParser
|
||||
*/
|
||||
private $dateParser;
|
||||
|
||||
/**
|
||||
* Hash algorithm used to generate item id, any value supported by PHP, see hash_algos().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $hash_algo = 'sha256';
|
||||
|
||||
/**
|
||||
* Feed content (XML data).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content = '';
|
||||
|
||||
/**
|
||||
* Fallback url.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fallback_url = '';
|
||||
|
||||
/**
|
||||
* XML namespaces supported by parser.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $namespaces = array();
|
||||
|
||||
/**
|
||||
* XML namespaces used in document.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $used_namespaces = array();
|
||||
|
||||
/**
|
||||
* Item Post Processor instance
|
||||
*
|
||||
* @access private
|
||||
* @var ItemPostProcessor
|
||||
*/
|
||||
private $itemPostProcessor;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $content Feed content
|
||||
* @param string $http_encoding HTTP encoding (headers)
|
||||
* @param string $fallback_url Fallback url when the feed provide relative or broken url
|
||||
*/
|
||||
public function __construct($content, $http_encoding = '', $fallback_url = '')
|
||||
{
|
||||
$this->fallback_url = $fallback_url;
|
||||
$xml_encoding = XmlParser::getEncodingFromXmlTag($content);
|
||||
|
||||
// Strip XML tag to avoid multiple encoding/decoding in the next XML processing
|
||||
$this->content = Filter::stripXmlTag($content);
|
||||
|
||||
// Encode everything in UTF-8
|
||||
Logger::setMessage(get_called_class().': HTTP Encoding "'.$http_encoding.'" ; XML Encoding "'.$xml_encoding.'"');
|
||||
$this->content = Encoding::convert($this->content, $xml_encoding ?: $http_encoding);
|
||||
|
||||
$this->itemPostProcessor = new ItemPostProcessor($this->config);
|
||||
$this->itemPostProcessor->register(new ContentGeneratorProcessor($this->config));
|
||||
$this->itemPostProcessor->register(new ContentFilterProcessor($this->config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the document.
|
||||
* @return Feed
|
||||
* @throws MalformedXmlException
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
Logger::setMessage(get_called_class().': begin parsing');
|
||||
|
||||
$xml = XmlParser::getSimpleXml($this->content);
|
||||
|
||||
if ($xml === false) {
|
||||
Logger::setMessage(get_called_class().': Applying XML workarounds');
|
||||
$this->content = Filter::normalizeData($this->content);
|
||||
$xml = XmlParser::getSimpleXml($this->content);
|
||||
|
||||
if ($xml === false) {
|
||||
Logger::setMessage(get_called_class().': XML parsing error');
|
||||
Logger::setMessage(XmlParser::getErrors());
|
||||
throw new MalformedXmlException('XML parsing error');
|
||||
}
|
||||
}
|
||||
|
||||
$this->used_namespaces = $xml->getNamespaces(true);
|
||||
$xml = $this->registerSupportedNamespaces($xml);
|
||||
|
||||
$feed = new Feed();
|
||||
|
||||
$this->findFeedUrl($xml, $feed);
|
||||
$this->checkFeedUrl($feed);
|
||||
|
||||
$this->findSiteUrl($xml, $feed);
|
||||
$this->checkSiteUrl($feed);
|
||||
|
||||
$this->findFeedTitle($xml, $feed);
|
||||
$this->findFeedDescription($xml, $feed);
|
||||
$this->findFeedLanguage($xml, $feed);
|
||||
$this->findFeedId($xml, $feed);
|
||||
$this->findFeedDate($xml, $feed);
|
||||
$this->findFeedLogo($xml, $feed);
|
||||
$this->findFeedIcon($xml, $feed);
|
||||
|
||||
foreach ($this->getItemsTree($xml) as $entry) {
|
||||
$entry = $this->registerSupportedNamespaces($entry);
|
||||
|
||||
$item = new Item();
|
||||
$item->xml = $entry;
|
||||
$item->namespaces = $this->used_namespaces;
|
||||
|
||||
$this->findItemAuthor($xml, $entry, $item);
|
||||
|
||||
$this->findItemUrl($entry, $item);
|
||||
$this->checkItemUrl($feed, $item);
|
||||
|
||||
$this->findItemTitle($entry, $item);
|
||||
$this->findItemContent($entry, $item);
|
||||
|
||||
// Id generation can use the item url/title/content (order is important)
|
||||
$this->findItemId($entry, $item, $feed);
|
||||
$this->findItemDate($entry, $item, $feed);
|
||||
$this->findItemEnclosure($entry, $item, $feed);
|
||||
$this->findItemLanguage($entry, $item, $feed);
|
||||
$this->findItemCategories($entry, $item, $feed);
|
||||
|
||||
$this->itemPostProcessor->execute($feed, $item);
|
||||
$feed->items[] = $item;
|
||||
}
|
||||
|
||||
Logger::setMessage(get_called_class().PHP_EOL.$feed);
|
||||
|
||||
return $feed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the feed url is correct.
|
||||
*
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function checkFeedUrl(Feed $feed)
|
||||
{
|
||||
if ($feed->getFeedUrl() === '') {
|
||||
$feed->feedUrl = $this->fallback_url;
|
||||
} else {
|
||||
$feed->feedUrl = Url::resolve($feed->getFeedUrl(), $this->fallback_url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the site url is correct.
|
||||
*
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function checkSiteUrl(Feed $feed)
|
||||
{
|
||||
if ($feed->getSiteUrl() === '') {
|
||||
$feed->siteUrl = Url::base($feed->getFeedUrl());
|
||||
} else {
|
||||
$feed->siteUrl = Url::resolve($feed->getSiteUrl(), $this->fallback_url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the item url is correct.
|
||||
*
|
||||
* @param Feed $feed Feed object
|
||||
* @param Item $item Item object
|
||||
*/
|
||||
public function checkItemUrl(Feed $feed, Item $item)
|
||||
{
|
||||
$item->url = Url::resolve($item->getUrl(), $feed->getSiteUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the item date.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
* @param \PicoFeed\Parser\Feed $feed Feed object
|
||||
*/
|
||||
public function findItemDate(SimpleXMLElement $entry, Item $item, Feed $feed)
|
||||
{
|
||||
$this->findItemPublishedDate($entry, $item, $feed);
|
||||
$this->findItemUpdatedDate($entry, $item, $feed);
|
||||
|
||||
if ($item->getPublishedDate() === null) {
|
||||
// Use the updated date if available, otherwise use the feed date
|
||||
$item->setPublishedDate($item->getUpdatedDate() ?: $feed->getDate());
|
||||
}
|
||||
|
||||
if ($item->getUpdatedDate() === null) {
|
||||
// Use the published date as fallback
|
||||
$item->setUpdatedDate($item->getPublishedDate());
|
||||
}
|
||||
|
||||
// Use the most recent of published and updated dates
|
||||
$item->setDate(max($item->getPublishedDate(), $item->getUpdatedDate()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Item Post Processor instance
|
||||
*
|
||||
* @access public
|
||||
* @return ItemPostProcessor
|
||||
*/
|
||||
public function getItemPostProcessor()
|
||||
{
|
||||
return $this->itemPostProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DateParser instance
|
||||
*
|
||||
* @access public
|
||||
* @return DateParser
|
||||
*/
|
||||
public function getDateParser()
|
||||
{
|
||||
if ($this->dateParser === null) {
|
||||
$this->dateParser = new DateParser($this->config);
|
||||
}
|
||||
|
||||
return $this->dateParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique id for an entry (hash all arguments).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateId()
|
||||
{
|
||||
return hash($this->hash_algo, implode(func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given language is "Right to Left".
|
||||
*
|
||||
* @static
|
||||
* @param string $language Language: fr-FR, en-US
|
||||
* @return bool
|
||||
*/
|
||||
public static function isLanguageRTL($language)
|
||||
{
|
||||
$language = strtolower($language);
|
||||
|
||||
$rtl_languages = array(
|
||||
'ar', // Arabic (ar-**)
|
||||
'fa', // Farsi (fa-**)
|
||||
'ur', // Urdu (ur-**)
|
||||
'ps', // Pashtu (ps-**)
|
||||
'syr', // Syriac (syr-**)
|
||||
'dv', // Divehi (dv-**)
|
||||
'he', // Hebrew (he-**)
|
||||
'yi', // Yiddish (yi-**)
|
||||
);
|
||||
|
||||
foreach ($rtl_languages as $prefix) {
|
||||
if (strpos($language, $prefix) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Hash algorithm used for id generation.
|
||||
*
|
||||
* @param string $algo Algorithm name
|
||||
* @return \PicoFeed\Parser\Parser
|
||||
*/
|
||||
public function setHashAlgo($algo)
|
||||
{
|
||||
$this->hash_algo = $algo ?: $this->hash_algo;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set config object.
|
||||
*
|
||||
* @param \PicoFeed\Config\Config $config Config instance
|
||||
* @return \PicoFeed\Parser\Parser
|
||||
*/
|
||||
public function setConfig($config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->itemPostProcessor->setConfig($config);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the content grabber.
|
||||
*
|
||||
* @return \PicoFeed\Parser\Parser
|
||||
*/
|
||||
public function disableContentFiltering()
|
||||
{
|
||||
$this->itemPostProcessor->unregister('PicoFeed\Processor\ContentFilterProcessor');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the content grabber.
|
||||
*
|
||||
* @param bool $needsRuleFile true if only pages with rule files should be
|
||||
* scraped
|
||||
* @param null|\Closure $scraperCallback Callback function that gets called for each
|
||||
* scraper execution
|
||||
* @return \PicoFeed\Parser\Parser
|
||||
*/
|
||||
public function enableContentGrabber($needsRuleFile = false, $scraperCallback = null)
|
||||
{
|
||||
$processor = new ScraperProcessor($this->config);
|
||||
|
||||
if ($needsRuleFile) {
|
||||
$processor->getScraper()->disableCandidateParser();
|
||||
}
|
||||
|
||||
if ($scraperCallback !== null) {
|
||||
$processor->setExecutionCallback($scraperCallback);
|
||||
}
|
||||
|
||||
$this->itemPostProcessor->register($processor);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ignored URLs for the content grabber.
|
||||
*
|
||||
* @param array $urls URLs
|
||||
* @return \PicoFeed\Parser\Parser
|
||||
*/
|
||||
public function setGrabberIgnoreUrls(array $urls)
|
||||
{
|
||||
$this->itemPostProcessor->getProcessor('PicoFeed\Processor\ScraperProcessor')->ignoreUrls($urls);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all supported namespaces to be used within an xpath query.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @return SimpleXMLElement
|
||||
*/
|
||||
public function registerSupportedNamespaces(SimpleXMLElement $xml)
|
||||
{
|
||||
foreach ($this->namespaces as $prefix => $ns) {
|
||||
$xml->registerXPathNamespace($prefix, $ns);
|
||||
}
|
||||
|
||||
return $xml;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Parser;
|
||||
|
||||
use PicoFeed\PicoFeedException;
|
||||
|
||||
/**
|
||||
* ParserException Exception.
|
||||
*
|
||||
* @package PicoFeed\Parser
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class ParserException extends PicoFeedException
|
||||
{
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace PicoFeed\Parser;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* Interface ParserInterface
|
||||
*
|
||||
* @package PicoFeed\Parser
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface ParserInterface
|
||||
{
|
||||
/**
|
||||
* Find the feed url.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedUrl(SimpleXMLElement $xml, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the site url.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findSiteUrl(SimpleXMLElement $xml, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the feed title.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedTitle(SimpleXMLElement $xml, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the feed description.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedDescription(SimpleXMLElement $xml, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the feed language.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedLanguage(SimpleXMLElement $xml, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the feed id.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedId(SimpleXMLElement $xml, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the feed date.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedDate(SimpleXMLElement $xml, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the feed logo url.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedLogo(SimpleXMLElement $xml, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the feed icon.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findFeedIcon(SimpleXMLElement $xml, Feed $feed);
|
||||
|
||||
/**
|
||||
* Get the path to the items XML tree.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed xml
|
||||
*
|
||||
* @return SimpleXMLElement
|
||||
*/
|
||||
public function getItemsTree(SimpleXMLElement $xml);
|
||||
|
||||
/**
|
||||
* Find the item author.
|
||||
*
|
||||
* @param SimpleXMLElement $xml Feed
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
*/
|
||||
public function findItemAuthor(SimpleXMLElement $xml, SimpleXMLElement $entry, Item $item);
|
||||
|
||||
/**
|
||||
* Find the item URL.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
*/
|
||||
public function findItemUrl(SimpleXMLElement $entry, Item $item);
|
||||
|
||||
/**
|
||||
* Find the item title.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
*/
|
||||
public function findItemTitle(SimpleXMLElement $entry, Item $item);
|
||||
|
||||
/**
|
||||
* Genereate the item id.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findItemId(SimpleXMLElement $entry, Item $item, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the item published date.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findItemPublishedDate(SimpleXMLElement $entry, Item $item, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the item updated date.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findItemUpdatedDate(SimpleXMLElement $entry, Item $item, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the item content.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
*/
|
||||
public function findItemContent(SimpleXMLElement $entry, Item $item);
|
||||
|
||||
/**
|
||||
* Find the item enclosure.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findItemEnclosure(SimpleXMLElement $entry, Item $item, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the item language.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findItemLanguage(SimpleXMLElement $entry, Item $item, Feed $feed);
|
||||
|
||||
/**
|
||||
* Find the item categories.
|
||||
*
|
||||
* @param SimpleXMLElement $entry Feed item
|
||||
* @param Item $item Item object
|
||||
* @param Feed $feed Feed object
|
||||
*/
|
||||
public function findItemCategories(SimpleXMLElement $entry, Item $item, Feed $feed);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user