看板初始化提交
This commit is contained in:
@@ -0,0 +1,739 @@
|
||||
<?php
|
||||
|
||||
namespace Gregwar\Captcha;
|
||||
|
||||
use \Exception;
|
||||
|
||||
/**
|
||||
* Builds a new captcha image
|
||||
* Uses the fingerprint parameter, if one is passed, to generate the same image
|
||||
*
|
||||
* @author Gregwar <g.passault@gmail.com>
|
||||
* @author Jeremy Livingston <jeremy.j.livingston@gmail.com>
|
||||
*/
|
||||
class CaptchaBuilder implements CaptchaBuilderInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fingerprint = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $useFingerprint = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $textColor = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $lineColor = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $backgroundColor = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $backgroundImages = array();
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $contents = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $phrase = null;
|
||||
|
||||
/**
|
||||
* @var PhraseBuilderInterface
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $distortion = true;
|
||||
|
||||
/**
|
||||
* The maximum number of lines to draw in front of
|
||||
* the image. null - use default algorithm
|
||||
*/
|
||||
protected $maxFrontLines = null;
|
||||
|
||||
/**
|
||||
* The maximum number of lines to draw behind
|
||||
* the image. null - use default algorithm
|
||||
*/
|
||||
protected $maxBehindLines = null;
|
||||
|
||||
/**
|
||||
* The maximum angle of char
|
||||
*/
|
||||
protected $maxAngle = 8;
|
||||
|
||||
/**
|
||||
* The maximum offset of char
|
||||
*/
|
||||
protected $maxOffset = 5;
|
||||
|
||||
/**
|
||||
* Is the interpolation enabled ?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $interpolation = true;
|
||||
|
||||
/**
|
||||
* Ignore all effects
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $ignoreAllEffects = false;
|
||||
|
||||
/**
|
||||
* Allowed image types for the background images
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $allowedBackgroundImageTypes = array('image/png', 'image/jpeg', 'image/gif');
|
||||
|
||||
/**
|
||||
* The image contents
|
||||
*/
|
||||
public function getContents()
|
||||
{
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/Disables the interpolation
|
||||
*
|
||||
* @param $interpolate bool True to enable, false to disable
|
||||
*
|
||||
* @return CaptchaBuilder
|
||||
*/
|
||||
public function setInterpolation($interpolate = true)
|
||||
{
|
||||
$this->interpolation = $interpolate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary dir, for OCR check
|
||||
*/
|
||||
public $tempDir = 'temp/';
|
||||
|
||||
public function __construct($phrase = null, ?PhraseBuilderInterface $builder = null)
|
||||
{
|
||||
if ($builder === null) {
|
||||
$this->builder = new PhraseBuilder;
|
||||
} else {
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
$this->phrase = is_string($phrase) ? $phrase : $this->builder->build($phrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting the phrase
|
||||
*/
|
||||
public function setPhrase($phrase)
|
||||
{
|
||||
$this->phrase = (string) $phrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disable distortion
|
||||
*/
|
||||
public function setDistortion($distortion)
|
||||
{
|
||||
$this->distortion = (bool) $distortion;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxBehindLines($maxBehindLines)
|
||||
{
|
||||
$this->maxBehindLines = $maxBehindLines;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxFrontLines($maxFrontLines)
|
||||
{
|
||||
$this->maxFrontLines = $maxFrontLines;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxAngle($maxAngle)
|
||||
{
|
||||
$this->maxAngle = $maxAngle;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxOffset($maxOffset)
|
||||
{
|
||||
$this->maxOffset = $maxOffset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the captcha phrase
|
||||
*/
|
||||
public function getPhrase()
|
||||
{
|
||||
return $this->phrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given phrase is good
|
||||
*/
|
||||
public function testPhrase($phrase)
|
||||
{
|
||||
return ($this->builder->niceize($phrase) == $this->builder->niceize($this->getPhrase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiation
|
||||
*/
|
||||
public static function create($phrase = null)
|
||||
{
|
||||
return new self($phrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text color to use
|
||||
*/
|
||||
public function setTextColor($r, $g, $b)
|
||||
{
|
||||
$this->textColor = array($r, $g, $b);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the background color to use
|
||||
*/
|
||||
public function setBackgroundColor($r, $g, $b)
|
||||
{
|
||||
$this->backgroundColor = array($r, $g, $b);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setLineColor($r, $g, $b)
|
||||
{
|
||||
$this->lineColor = array($r, $g, $b);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ignoreAllEffects value
|
||||
*
|
||||
* @param bool $ignoreAllEffects
|
||||
* @return CaptchaBuilder
|
||||
*/
|
||||
public function setIgnoreAllEffects($ignoreAllEffects)
|
||||
{
|
||||
$this->ignoreAllEffects = $ignoreAllEffects;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of background images to use (one image is randomly selected)
|
||||
*/
|
||||
public function setBackgroundImages(array $backgroundImages)
|
||||
{
|
||||
$this->backgroundImages = $backgroundImages;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw lines over the image
|
||||
*/
|
||||
protected function drawLine($image, $width, $height, $tcol = null)
|
||||
{
|
||||
if ($this->lineColor === null) {
|
||||
$red = $this->rand(100, 255);
|
||||
$green = $this->rand(100, 255);
|
||||
$blue = $this->rand(100, 255);
|
||||
} else {
|
||||
$red = $this->lineColor[0];
|
||||
$green = $this->lineColor[1];
|
||||
$blue = $this->lineColor[2];
|
||||
}
|
||||
|
||||
if ($tcol === null) {
|
||||
$tcol = imagecolorallocate($image, $red, $green, $blue);
|
||||
}
|
||||
|
||||
if ($this->rand(0, 1)) { // Horizontal
|
||||
$Xa = $this->rand(0, $width/2);
|
||||
$Ya = $this->rand(0, $height);
|
||||
$Xb = $this->rand($width/2, $width);
|
||||
$Yb = $this->rand(0, $height);
|
||||
} else { // Vertical
|
||||
$Xa = $this->rand(0, $width);
|
||||
$Ya = $this->rand(0, $height/2);
|
||||
$Xb = $this->rand(0, $width);
|
||||
$Yb = $this->rand($height/2, $height);
|
||||
}
|
||||
imagesetthickness($image, $this->rand(1, 3));
|
||||
imageline($image, $Xa, $Ya, $Xb, $Yb, $tcol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply some post effects
|
||||
*/
|
||||
protected function postEffect($image)
|
||||
{
|
||||
if (!function_exists('imagefilter')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->backgroundColor != null || $this->textColor != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Negate ?
|
||||
if ($this->rand(0, 1) == 0) {
|
||||
imagefilter($image, IMG_FILTER_NEGATE);
|
||||
}
|
||||
|
||||
// Edge ?
|
||||
if ($this->rand(0, 10) == 0) {
|
||||
imagefilter($image, IMG_FILTER_EDGEDETECT);
|
||||
}
|
||||
|
||||
// Contrast
|
||||
imagefilter($image, IMG_FILTER_CONTRAST, $this->rand(-50, 10));
|
||||
|
||||
// Colorize
|
||||
if ($this->rand(0, 5) == 0) {
|
||||
imagefilter($image, IMG_FILTER_COLORIZE, $this->rand(-80, 50), $this->rand(-80, 50), $this->rand(-80, 50));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the phrase on the image
|
||||
*/
|
||||
protected function writePhrase($image, $phrase, $font, $width, $height)
|
||||
{
|
||||
$length = mb_strlen($phrase);
|
||||
if ($length === 0) {
|
||||
return \imagecolorallocate($image, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Gets the text size and start position
|
||||
$size = $width / $length - $this->rand(0, 3) - 1;
|
||||
$box = \imagettfbbox($size, 0, $font, $phrase);
|
||||
$textWidth = $box[2] - $box[0];
|
||||
$textHeight = $box[1] - $box[7];
|
||||
$x = ($width - $textWidth) / 2;
|
||||
$y = ($height - $textHeight) / 2 + $size;
|
||||
|
||||
if (!$this->textColor) {
|
||||
$textColor = array($this->rand(0, 150), $this->rand(0, 150), $this->rand(0, 150));
|
||||
} else {
|
||||
$textColor = $this->textColor;
|
||||
}
|
||||
$col = \imagecolorallocate($image, $textColor[0], $textColor[1], $textColor[2]);
|
||||
|
||||
// Write the letters one by one, with random angle
|
||||
for ($i=0; $i<$length; $i++) {
|
||||
$symbol = mb_substr($phrase, $i, 1);
|
||||
$box = \imagettfbbox($size, 0, $font, $symbol);
|
||||
$w = $box[2] - $box[0];
|
||||
$angle = $this->rand(-$this->maxAngle, $this->maxAngle);
|
||||
$offset = $this->rand(-$this->maxOffset, $this->maxOffset);
|
||||
\imagettftext($image, (float) $size, (float) $angle, (int) $x, (int) $y + $offset, (int) $col, (string) $font, (string) $symbol);
|
||||
$x += $w;
|
||||
}
|
||||
|
||||
return $col;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to read the code against an OCR
|
||||
*/
|
||||
public function isOCRReadable()
|
||||
{
|
||||
if (!is_dir($this->tempDir)) {
|
||||
@mkdir($this->tempDir, 0o755, true);
|
||||
}
|
||||
|
||||
$tempj = $this->tempDir . uniqid('captcha', true) . '.jpg';
|
||||
$tempp = $this->tempDir . uniqid('captcha', true) . '.pgm';
|
||||
|
||||
$this->save($tempj);
|
||||
shell_exec("convert $tempj $tempp");
|
||||
$value = trim(strtolower(shell_exec("ocrad $tempp")));
|
||||
|
||||
@unlink($tempj);
|
||||
@unlink($tempp);
|
||||
|
||||
return $this->testPhrase($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds while the code is readable against an OCR
|
||||
*/
|
||||
public function buildAgainstOCR($width = 150, $height = 40, $font = null, $fingerprint = null)
|
||||
{
|
||||
do {
|
||||
$this->build($width, $height, $font, $fingerprint);
|
||||
} while ($this->isOCRReadable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the image
|
||||
*/
|
||||
public function build($width = 150, $height = 40, $font = null, $fingerprint = null)
|
||||
{
|
||||
if (null !== $fingerprint) {
|
||||
$this->fingerprint = $fingerprint;
|
||||
$this->useFingerprint = true;
|
||||
} else {
|
||||
$this->fingerprint = array();
|
||||
$this->useFingerprint = false;
|
||||
}
|
||||
|
||||
if ($font === null) {
|
||||
$font = __DIR__ . '/Font/captcha'.$this->rand(0, 5).'.ttf';
|
||||
}
|
||||
|
||||
if (empty($this->backgroundImages)) {
|
||||
// if background images list is not set, use a color fill as a background
|
||||
$image = imagecreatetruecolor($width, $height);
|
||||
if ($this->backgroundColor == null) {
|
||||
$bg = imagecolorallocate($image, $this->rand(200, 255), $this->rand(200, 255), $this->rand(200, 255));
|
||||
} else {
|
||||
$color = $this->backgroundColor;
|
||||
$bg = imagecolorallocate($image, $color[0], $color[1], $color[2]);
|
||||
}
|
||||
$this->backgroundColor = $bg;
|
||||
imagefill($image, 0, 0, $bg);
|
||||
} else {
|
||||
// use a random background image
|
||||
$randomBackgroundImage = $this->backgroundImages[rand(0, count($this->backgroundImages)-1)];
|
||||
|
||||
$imageType = $this->validateBackgroundImage($randomBackgroundImage);
|
||||
|
||||
$image = $this->createBackgroundImageFromType($randomBackgroundImage, $imageType);
|
||||
}
|
||||
|
||||
// Apply effects
|
||||
if (!$this->ignoreAllEffects) {
|
||||
$square = $width * $height;
|
||||
$effects = $this->rand($square/3000, $square/2000);
|
||||
|
||||
// set the maximum number of lines to draw in front of the text
|
||||
if ($this->maxBehindLines != null && $this->maxBehindLines > 0) {
|
||||
$effects = min($this->maxBehindLines, $effects);
|
||||
}
|
||||
|
||||
if ($this->maxBehindLines !== 0) {
|
||||
for ($e = 0; $e < $effects; $e++) {
|
||||
$this->drawLine($image, $width, $height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write CAPTCHA text
|
||||
$color = $this->writePhrase($image, $this->phrase, $font, $width, $height);
|
||||
|
||||
// Apply effects
|
||||
if (!$this->ignoreAllEffects) {
|
||||
$square = $width * $height;
|
||||
$effects = $this->rand($square/3000, $square/2000);
|
||||
|
||||
// set the maximum number of lines to draw in front of the text
|
||||
if ($this->maxFrontLines != null && $this->maxFrontLines > 0) {
|
||||
$effects = min($this->maxFrontLines, $effects);
|
||||
}
|
||||
|
||||
if ($this->maxFrontLines !== 0) {
|
||||
for ($e = 0; $e < $effects; $e++) {
|
||||
$this->drawLine($image, $width, $height, $color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Distort the image
|
||||
if ($this->distortion && !$this->ignoreAllEffects) {
|
||||
$image = $this->distort($image, $width, $height, $bg);
|
||||
}
|
||||
|
||||
// Post effects
|
||||
if (!$this->ignoreAllEffects) {
|
||||
$this->postEffect($image);
|
||||
}
|
||||
|
||||
$this->contents = $image;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Distorts the image
|
||||
*/
|
||||
public function distort($image, $width, $height, $bg)
|
||||
{
|
||||
$contents = imagecreatetruecolor($width, $height);
|
||||
$X = $this->rand(0, $width);
|
||||
$Y = $this->rand(0, $height);
|
||||
$phase = $this->rand(0, 10);
|
||||
$scale = 1.1 + $this->rand(0, 10000) / 30000;
|
||||
for ($x = 0; $x < $width; $x++) {
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
$Vx = $x - $X;
|
||||
$Vy = $y - $Y;
|
||||
$Vn = sqrt($Vx * $Vx + $Vy * $Vy);
|
||||
|
||||
if ($Vn != 0) {
|
||||
$Vn2 = $Vn + 4 * sin($Vn / 30);
|
||||
$nX = $X + ($Vx * $Vn2 / $Vn);
|
||||
$nY = $Y + ($Vy * $Vn2 / $Vn);
|
||||
} else {
|
||||
$nX = $X;
|
||||
$nY = $Y;
|
||||
}
|
||||
$nY = $nY + $scale * sin($phase + $nX * 0.2);
|
||||
|
||||
if ($this->interpolation) {
|
||||
$p = $this->interpolate(
|
||||
$nX - floor($nX),
|
||||
$nY - floor($nY),
|
||||
$this->getCol($image, floor($nX), floor($nY), $bg),
|
||||
$this->getCol($image, ceil($nX), floor($nY), $bg),
|
||||
$this->getCol($image, floor($nX), ceil($nY), $bg),
|
||||
$this->getCol($image, ceil($nX), ceil($nY), $bg)
|
||||
);
|
||||
} else {
|
||||
$p = $this->getCol($image, round($nX), round($nY), $bg);
|
||||
}
|
||||
|
||||
if ($p == 0) {
|
||||
$p = $bg;
|
||||
}
|
||||
|
||||
imagesetpixel($contents, $x, $y, $p);
|
||||
}
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Captcha to a jpeg file
|
||||
*/
|
||||
public function save($filename, $quality = 90)
|
||||
{
|
||||
imagejpeg($this->contents, $filename, $quality);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image GD
|
||||
*/
|
||||
public function getGd()
|
||||
{
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image contents
|
||||
*/
|
||||
public function get($quality = 90)
|
||||
{
|
||||
ob_start();
|
||||
$this->output($quality);
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTML inline base64
|
||||
*/
|
||||
public function inline($quality = 90)
|
||||
{
|
||||
return 'data:image/jpeg;base64,' . base64_encode($this->get($quality));
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the image
|
||||
*/
|
||||
public function output($quality = 90)
|
||||
{
|
||||
imagejpeg($this->contents, null, $quality);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFingerprint()
|
||||
{
|
||||
return $this->fingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random number or the next number in the
|
||||
* fingerprint
|
||||
*/
|
||||
protected function rand($min, $max)
|
||||
{
|
||||
if (!is_array($this->fingerprint)) {
|
||||
$this->fingerprint = array();
|
||||
}
|
||||
|
||||
if ($this->useFingerprint) {
|
||||
$value = current($this->fingerprint);
|
||||
next($this->fingerprint);
|
||||
} else {
|
||||
$value = mt_rand($min, $max);
|
||||
$this->fingerprint[] = $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $x
|
||||
* @param $y
|
||||
* @param $nw
|
||||
* @param $ne
|
||||
* @param $sw
|
||||
* @param $se
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function interpolate($x, $y, $nw, $ne, $sw, $se)
|
||||
{
|
||||
list($r0, $g0, $b0) = $this->getRGB($nw);
|
||||
list($r1, $g1, $b1) = $this->getRGB($ne);
|
||||
list($r2, $g2, $b2) = $this->getRGB($sw);
|
||||
list($r3, $g3, $b3) = $this->getRGB($se);
|
||||
|
||||
$cx = 1.0 - $x;
|
||||
$cy = 1.0 - $y;
|
||||
|
||||
$m0 = $cx * $r0 + $x * $r1;
|
||||
$m1 = $cx * $r2 + $x * $r3;
|
||||
$r = (int) ($cy * $m0 + $y * $m1);
|
||||
|
||||
$m0 = $cx * $g0 + $x * $g1;
|
||||
$m1 = $cx * $g2 + $x * $g3;
|
||||
$g = (int) ($cy * $m0 + $y * $m1);
|
||||
|
||||
$m0 = $cx * $b0 + $x * $b1;
|
||||
$m1 = $cx * $b2 + $x * $b3;
|
||||
$b = (int) ($cy * $m0 + $y * $m1);
|
||||
|
||||
return ($r << 16) | ($g << 8) | $b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $image
|
||||
* @param $x
|
||||
* @param $y
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getCol($image, $x, $y, $background)
|
||||
{
|
||||
$L = imagesx($image);
|
||||
$H = imagesy($image);
|
||||
if ($x < 0 || $x >= $L || $y < 0 || $y >= $H) {
|
||||
return $background;
|
||||
}
|
||||
|
||||
return imagecolorat($image, $x, $y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $col
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getRGB($col)
|
||||
{
|
||||
return array(
|
||||
(int) ($col >> 16) & 0xff,
|
||||
(int) ($col >> 8) & 0xff,
|
||||
(int) ($col) & 0xff,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the background image path. Return the image type if valid
|
||||
*
|
||||
* @param string $backgroundImage
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function validateBackgroundImage($backgroundImage)
|
||||
{
|
||||
// check if file exists
|
||||
if (!file_exists($backgroundImage)) {
|
||||
$backgroundImageExploded = explode('/', $backgroundImage);
|
||||
$imageFileName = count($backgroundImageExploded) > 1? $backgroundImageExploded[count($backgroundImageExploded)-1] : $backgroundImage;
|
||||
|
||||
throw new Exception('Invalid background image: ' . $imageFileName);
|
||||
}
|
||||
|
||||
// check image type
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
|
||||
$imageType = finfo_file($finfo, $backgroundImage);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (!in_array($imageType, $this->allowedBackgroundImageTypes)) {
|
||||
throw new Exception('Invalid background image type! Allowed types are: ' . join(', ', $this->allowedBackgroundImageTypes));
|
||||
}
|
||||
|
||||
return $imageType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create background image from type
|
||||
*
|
||||
* @param string $backgroundImage
|
||||
* @param string $imageType
|
||||
* @return resource
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function createBackgroundImageFromType($backgroundImage, $imageType)
|
||||
{
|
||||
switch ($imageType) {
|
||||
case 'image/jpeg':
|
||||
$image = imagecreatefromjpeg($backgroundImage);
|
||||
break;
|
||||
case 'image/png':
|
||||
$image = imagecreatefrompng($backgroundImage);
|
||||
break;
|
||||
case 'image/gif':
|
||||
$image = imagecreatefromgif($backgroundImage);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Not supported file type for background image!');
|
||||
break;
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Gregwar\Captcha;
|
||||
|
||||
/**
|
||||
* A Captcha builder
|
||||
*/
|
||||
interface CaptchaBuilderInterface
|
||||
{
|
||||
/**
|
||||
* Builds the code
|
||||
*/
|
||||
public function build($width, $height, $font, $fingerprint);
|
||||
|
||||
/**
|
||||
* Saves the code to a file
|
||||
*/
|
||||
public function save($filename, $quality);
|
||||
|
||||
/**
|
||||
* Gets the image contents
|
||||
*/
|
||||
public function get($quality);
|
||||
|
||||
/**
|
||||
* Outputs the image
|
||||
*/
|
||||
public function output($quality);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Gregwar\Captcha;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
/**
|
||||
* Handles actions related to captcha image files including saving and garbage collection
|
||||
*
|
||||
* @author Gregwar <g.passault@gmail.com>
|
||||
* @author Jeremy Livingston <jeremy@quizzle.com>
|
||||
*/
|
||||
class ImageFileHandler
|
||||
{
|
||||
/**
|
||||
* Name of folder for captcha images
|
||||
* @var string
|
||||
*/
|
||||
protected $imageFolder;
|
||||
|
||||
/**
|
||||
* Absolute path to public web folder
|
||||
* @var string
|
||||
*/
|
||||
protected $webPath;
|
||||
|
||||
/**
|
||||
* Frequency of garbage collection in fractions of 1
|
||||
* @var int
|
||||
*/
|
||||
protected $gcFreq;
|
||||
|
||||
/**
|
||||
* Maximum age of images in minutes
|
||||
* @var int
|
||||
*/
|
||||
protected $expiration;
|
||||
|
||||
/**
|
||||
* @param $imageFolder
|
||||
* @param $webPath
|
||||
* @param $gcFreq
|
||||
* @param $expiration
|
||||
*/
|
||||
public function __construct($imageFolder, $webPath, $gcFreq, $expiration)
|
||||
{
|
||||
$this->imageFolder = $imageFolder;
|
||||
$this->webPath = $webPath;
|
||||
$this->gcFreq = $gcFreq;
|
||||
$this->expiration = $expiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the provided image content as a file
|
||||
*
|
||||
* @param string $contents
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function saveAsFile($contents)
|
||||
{
|
||||
$this->createFolderIfMissing();
|
||||
|
||||
$filename = md5(uniqid()) . '.jpg';
|
||||
$filePath = $this->webPath . '/' . $this->imageFolder . '/' . $filename;
|
||||
imagejpeg($contents, $filePath, 15);
|
||||
|
||||
return '/' . $this->imageFolder . '/' . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly runs garbage collection on the image directory
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function collectGarbage()
|
||||
{
|
||||
if (!mt_rand(1, $this->gcFreq) == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->createFolderIfMissing();
|
||||
|
||||
$finder = new Finder();
|
||||
$criteria = sprintf('<= now - %s minutes', $this->expiration);
|
||||
$finder->in($this->webPath . '/' . $this->imageFolder)
|
||||
->date($criteria);
|
||||
|
||||
foreach ($finder->files() as $file) {
|
||||
unlink($file->getPathname());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the folder if it doesn't exist
|
||||
*/
|
||||
protected function createFolderIfMissing()
|
||||
{
|
||||
if (!file_exists($this->webPath . '/' . $this->imageFolder)) {
|
||||
mkdir($this->webPath . '/' . $this->imageFolder, 0o755);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) <2012-2017> Grégoire Passault
|
||||
|
||||
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,75 @@
|
||||
<?php
|
||||
|
||||
namespace Gregwar\Captcha;
|
||||
|
||||
/**
|
||||
* Generates random phrase
|
||||
*
|
||||
* @author Gregwar <g.passault@gmail.com>
|
||||
*/
|
||||
class PhraseBuilder implements PhraseBuilderInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $length;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $charset;
|
||||
/**
|
||||
* Constructs a PhraseBuilder with given parameters
|
||||
*/
|
||||
public function __construct($length = 5, $charset = 'abcdefghijklmnpqrstuvwxyz123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||
{
|
||||
$this->length = $length;
|
||||
$this->charset = $charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates random phrase of given length with given charset
|
||||
*/
|
||||
public function build($length = null, $charset = null)
|
||||
{
|
||||
if ($length !== null) {
|
||||
$this->length = $length;
|
||||
}
|
||||
if ($charset !== null) {
|
||||
$this->charset = $charset;
|
||||
}
|
||||
|
||||
$phrase = '';
|
||||
$chars = str_split($this->charset);
|
||||
|
||||
for ($i = 0; $i < $this->length; $i++) {
|
||||
$phrase .= $chars[array_rand($chars)];
|
||||
}
|
||||
|
||||
return $phrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Niceize" a code
|
||||
*/
|
||||
public function niceize($str)
|
||||
{
|
||||
return self::doNiceize($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* A static helper to niceize
|
||||
*/
|
||||
public static function doNiceize($str)
|
||||
{
|
||||
return strtr(strtolower($str), '01', 'ol');
|
||||
}
|
||||
|
||||
/**
|
||||
* A static helper to compare
|
||||
*/
|
||||
public static function comparePhrases($str1, $str2)
|
||||
{
|
||||
return self::doNiceize($str1) === self::doNiceize($str2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Gregwar\Captcha;
|
||||
|
||||
/**
|
||||
* Interface for the PhraseBuilder
|
||||
*
|
||||
* @author Gregwar <g.passault@gmail.com>
|
||||
*/
|
||||
interface PhraseBuilderInterface
|
||||
{
|
||||
/**
|
||||
* Generates random phrase of given length with given charset
|
||||
*/
|
||||
public function build();
|
||||
|
||||
/**
|
||||
* "Niceize" a code
|
||||
*/
|
||||
public function niceize($str);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleQueue\Adapter;
|
||||
|
||||
use DateTime;
|
||||
use PhpAmqpLib\Channel\AMQPChannel;
|
||||
use PhpAmqpLib\Message\AMQPMessage;
|
||||
use PhpAmqpLib\Wire\AMQPTable;
|
||||
use SimpleQueue\Job;
|
||||
use SimpleQueue\QueueAdapterInterface;
|
||||
|
||||
/**
|
||||
* Class AmqpQueueAdapter
|
||||
*
|
||||
* @package SimpleQueue\Adapter
|
||||
*/
|
||||
class AmqpQueueAdapter implements QueueAdapterInterface
|
||||
{
|
||||
/**
|
||||
* @var AMQPChannel
|
||||
*/
|
||||
protected $channel;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $exchange = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $queue = '';
|
||||
|
||||
/**
|
||||
* AmqpQueueAdapter constructor.
|
||||
*
|
||||
* @param AMQPChannel $channel
|
||||
* @param string $queue
|
||||
* @param string $exchange
|
||||
*/
|
||||
public function __construct(AMQPChannel $channel, $queue, $exchange)
|
||||
{
|
||||
$this->channel = $channel;
|
||||
$this->exchange = $exchange;
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a job
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function push(Job $job)
|
||||
{
|
||||
$message = new AMQPMessage($job->serialize(), array('content_type' => 'text/plain'));
|
||||
$this->channel->basic_publish($message, $this->exchange);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a job in the future
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @param DateTime $dateTime
|
||||
* @return $this
|
||||
*/
|
||||
public function schedule(Job $job, DateTime $dateTime)
|
||||
{
|
||||
$now = new DateTime();
|
||||
$when = clone($dateTime);
|
||||
$delay = $when->getTimestamp() - $now->getTimestamp();
|
||||
|
||||
$message = new AMQPMessage($job->serialize(), array('delivery_mode' => 2));
|
||||
$message->set('application_headers', new AMQPTable(array('x-delay' => $delay)));
|
||||
|
||||
$this->channel->basic_publish($message, $this->exchange);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait and get job from a queue
|
||||
*
|
||||
* @access public
|
||||
* @return Job|null
|
||||
*/
|
||||
public function pull()
|
||||
{
|
||||
$message = null;
|
||||
|
||||
$this->channel->basic_consume($this->queue, 'test', false, false, false, false, function ($msg) use (&$message) {
|
||||
$message = $msg;
|
||||
$message->delivery_info['channel']->basic_cancel($message->delivery_info['consumer_tag']);
|
||||
});
|
||||
|
||||
while (count($this->channel->callbacks)) {
|
||||
$this->channel->wait();
|
||||
}
|
||||
|
||||
if ($message === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$job = new Job();
|
||||
$job->setId($message->get('delivery_tag'));
|
||||
$job->unserialize($message->getBody());
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledge a job
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function completed(Job $job)
|
||||
{
|
||||
$this->channel->basic_ack($job->getId());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a job as failed
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function failed(Job $job)
|
||||
{
|
||||
$this->channel->basic_nack($job->getId());
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleQueue\Adapter;
|
||||
|
||||
use DateTime;
|
||||
use Pheanstalk\Job as BeanstalkJob;
|
||||
use Pheanstalk\Pheanstalk;
|
||||
use Pheanstalk\PheanstalkInterface;
|
||||
use SimpleQueue\Job;
|
||||
use SimpleQueue\QueueAdapterInterface;
|
||||
|
||||
/**
|
||||
* Class BeanstalkQueueAdapter
|
||||
*
|
||||
* @package SimpleQueue\Adapter
|
||||
*/
|
||||
class BeanstalkQueueAdapter implements QueueAdapterInterface
|
||||
{
|
||||
/**
|
||||
* @var PheanstalkInterface
|
||||
*/
|
||||
protected $beanstalk;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $queueName = '';
|
||||
|
||||
/**
|
||||
* BeanstalkQueueAdapter constructor.
|
||||
*
|
||||
* @param PheanstalkInterface $beanstalk
|
||||
* @param string $queueName
|
||||
*/
|
||||
public function __construct(PheanstalkInterface $beanstalk, $queueName)
|
||||
{
|
||||
$this->beanstalk = $beanstalk;
|
||||
$this->queueName = $queueName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a job
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function push(Job $job)
|
||||
{
|
||||
$this->beanstalk->putInTube($this->queueName, $job->serialize());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a job in the future
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @param DateTime $dateTime
|
||||
* @return $this
|
||||
*/
|
||||
public function schedule(Job $job, DateTime $dateTime)
|
||||
{
|
||||
$now = new DateTime();
|
||||
$when = clone($dateTime);
|
||||
$delay = $when->getTimestamp() - $now->getTimestamp();
|
||||
|
||||
$this->beanstalk->putInTube($this->queueName, $job->serialize(), Pheanstalk::DEFAULT_PRIORITY, $delay);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait and get job from a queue
|
||||
*
|
||||
* @access public
|
||||
* @return Job|null
|
||||
*/
|
||||
public function pull()
|
||||
{
|
||||
$beanstalkJob = $this->beanstalk->reserveFromTube($this->queueName);
|
||||
|
||||
if ($beanstalkJob === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$job = new Job();
|
||||
$job->setId($beanstalkJob->getId());
|
||||
$job->unserialize($beanstalkJob->getData());
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledge a job
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function completed(Job $job)
|
||||
{
|
||||
$beanstalkJob = new BeanstalkJob($job->getId(), $job->serialize());
|
||||
$this->beanstalk->delete($beanstalkJob);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a job as failed
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function failed(Job $job)
|
||||
{
|
||||
$beanstalkJob = new BeanstalkJob($job->getId(), $job->serialize());
|
||||
$this->beanstalk->bury($beanstalkJob);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleQueue\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class NotSupportedException
|
||||
*
|
||||
* @package SimpleQueue\Exception
|
||||
*/
|
||||
class NotSupportedException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleQueue;
|
||||
|
||||
/**
|
||||
* Class Job
|
||||
*
|
||||
* @package SimpleQueue
|
||||
*/
|
||||
class Job
|
||||
{
|
||||
protected $id;
|
||||
protected $body;
|
||||
|
||||
/**
|
||||
* Job constructor.
|
||||
*
|
||||
* @param null $body
|
||||
* @param null $id
|
||||
*/
|
||||
public function __construct($body = null, $id = null)
|
||||
{
|
||||
$this->body = $body;
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize a payload
|
||||
*
|
||||
* @param string $payload
|
||||
* @return $this
|
||||
*/
|
||||
public function unserialize($payload)
|
||||
{
|
||||
$this->body = json_decode($payload, true);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the body
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return json_encode($this->body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set body
|
||||
*
|
||||
* @param mixed $body
|
||||
* @return Job
|
||||
*/
|
||||
public function setBody($body)
|
||||
{
|
||||
$this->body = $body;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get body
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set job ID
|
||||
*
|
||||
* @param mixed $jobId
|
||||
* @return Job
|
||||
*/
|
||||
public function setId($jobId)
|
||||
{
|
||||
$this->id = $jobId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get job ID
|
||||
* @return mixed
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute job
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleQueue;
|
||||
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Class Queue
|
||||
*
|
||||
* @package SimpleQueue
|
||||
*/
|
||||
class Queue implements QueueAdapterInterface
|
||||
{
|
||||
/**
|
||||
* @var QueueAdapterInterface
|
||||
*/
|
||||
protected $queueAdapter;
|
||||
|
||||
/**
|
||||
* Queue constructor.
|
||||
*
|
||||
* @param QueueAdapterInterface $queueAdapter
|
||||
*/
|
||||
public function __construct(QueueAdapterInterface $queueAdapter)
|
||||
{
|
||||
$this->queueAdapter = $queueAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a job
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function push(Job $job)
|
||||
{
|
||||
$this->queueAdapter->push($job);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a job in the future
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @param DateTime $dateTime
|
||||
* @return $this
|
||||
*/
|
||||
public function schedule(Job $job, DateTime $dateTime)
|
||||
{
|
||||
$this->queueAdapter->schedule($job, $dateTime);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait and get job from a queue
|
||||
*
|
||||
* @access public
|
||||
* @return Job|null
|
||||
*/
|
||||
public function pull()
|
||||
{
|
||||
return $this->queueAdapter->pull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledge a job
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function completed(Job $job)
|
||||
{
|
||||
$this->queueAdapter->completed($job);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a job as failed
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function failed(Job $job)
|
||||
{
|
||||
$this->queueAdapter->failed($job);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleQueue;
|
||||
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Interface AdapterInterface
|
||||
*
|
||||
* @package SimpleQueue\Adapter
|
||||
*/
|
||||
interface QueueAdapterInterface
|
||||
{
|
||||
/**
|
||||
* Send a job
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function push(Job $job);
|
||||
|
||||
/**
|
||||
* Schedule a job in the future
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @param DateTime $dateTime
|
||||
* @return $this
|
||||
*/
|
||||
public function schedule(Job $job, DateTime $dateTime);
|
||||
|
||||
/**
|
||||
* Wait and get job from a queue
|
||||
*
|
||||
* @access public
|
||||
* @return Job|null
|
||||
*/
|
||||
public function pull();
|
||||
|
||||
/**
|
||||
* Acknowledge a job
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function completed(Job $job);
|
||||
|
||||
/**
|
||||
* Mark a job as failed
|
||||
*
|
||||
* @access public
|
||||
* @param Job $job
|
||||
* @return $this
|
||||
*/
|
||||
public function failed(Job $job);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator;
|
||||
|
||||
class Validator
|
||||
{
|
||||
private $data = array();
|
||||
private $errors = array();
|
||||
private $validators = array();
|
||||
|
||||
public function __construct(array $data, array $validators)
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->validators = $validators;
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$result = true;
|
||||
|
||||
foreach ($this->validators as $validator) {
|
||||
if (! $validator->execute($this->data)) {
|
||||
$this->addError($validator->getField(), $validator->getErrorMessage());
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function addError($field, $message)
|
||||
{
|
||||
if (! isset($this->errors[$field])) {
|
||||
$this->errors[$field] = array();
|
||||
}
|
||||
|
||||
$this->errors[$field][] = $message;
|
||||
}
|
||||
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class Alpha extends Base
|
||||
{
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
return ctype_alpha($data[$this->field]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class AlphaNumeric extends Base
|
||||
{
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
return ctype_alnum($data[$this->field]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
abstract class Base
|
||||
{
|
||||
protected $field = '';
|
||||
protected $error_message = '';
|
||||
protected $data = array();
|
||||
|
||||
abstract public function execute(array $data);
|
||||
|
||||
public function __construct($field, $error_message)
|
||||
{
|
||||
$this->field = $field;
|
||||
$this->error_message = $error_message;
|
||||
}
|
||||
|
||||
public function getErrorMessage()
|
||||
{
|
||||
return $this->error_message;
|
||||
}
|
||||
|
||||
public function getField()
|
||||
{
|
||||
if (is_array($this->field)) {
|
||||
return $this->field[0];
|
||||
}
|
||||
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
public function isFieldNotEmpty(array $data)
|
||||
{
|
||||
return isset($data[$this->field]) && $data[$this->field] !== '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
use DateTime;
|
||||
|
||||
class Date extends Base
|
||||
{
|
||||
private $formats = array();
|
||||
|
||||
public function __construct($field, $error_message, array $formats)
|
||||
{
|
||||
parent::__construct($field, $error_message);
|
||||
$this->formats = $formats;
|
||||
}
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
foreach ($this->formats as $format) {
|
||||
if ($this->isValidDate($data[$this->field], $format)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isValidDate($value, $format)
|
||||
{
|
||||
$date = DateTime::createFromFormat($format, $value);
|
||||
|
||||
if ($date !== false) {
|
||||
$errors = DateTime::getLastErrors();
|
||||
if ($errors === false ||
|
||||
$errors['error_count'] === 0 && $errors['warning_count'] === 0) {
|
||||
return $date->getTimestamp() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class Email extends Base
|
||||
{
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
|
||||
// I use the same validation method as Firefox
|
||||
// http://hg.mozilla.org/mozilla-central/file/cf5da681d577/content/html/content/src/nsHTMLInputElement.cpp#l3967
|
||||
|
||||
$value = $data[$this->field];
|
||||
$length = strlen($value);
|
||||
|
||||
// If the email address begins with a '@' or ends with a '.',
|
||||
// we know it's invalid.
|
||||
if ($value[0] === '@' || $value[$length - 1] === '.') {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the username
|
||||
for ($i = 0; $i < $length && $value[$i] !== '@'; ++$i) {
|
||||
|
||||
$c = $value[$i];
|
||||
|
||||
if (! (ctype_alnum($c) || $c === '.' || $c === '!' || $c === '#' || $c === '$' ||
|
||||
$c === '%' || $c === '&' || $c === '\'' || $c === '*' || $c === '+' ||
|
||||
$c === '-' || $c === '/' || $c === '=' || $c === '?' || $c === '^' ||
|
||||
$c === '_' || $c === '`' || $c === '{' || $c === '|' || $c === '}' ||
|
||||
$c === '~')) {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// There is no domain name (or it's one-character long),
|
||||
// that's not a valid email address.
|
||||
if (++$i >= $length) return false;
|
||||
if (($i + 1) === $length) return false;
|
||||
|
||||
// The domain name can't begin with a dot.
|
||||
if ($value[$i] === '.') return false;
|
||||
|
||||
// Parsing the domain name.
|
||||
for (; $i < $length; ++$i) {
|
||||
|
||||
$c = $value[$i];
|
||||
|
||||
if ($c === '.') {
|
||||
|
||||
// A dot can't follow a dot.
|
||||
if ($value[$i - 1] === '.') return false;
|
||||
}
|
||||
elseif (! (ctype_alnum($c) || $c === '-')) {
|
||||
|
||||
// The domain characters have to be in this list to be valid.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class Equals extends Base
|
||||
{
|
||||
private $field2;
|
||||
|
||||
public function __construct($field1, $field2, $error_message)
|
||||
{
|
||||
parent::__construct($field1, $error_message);
|
||||
$this->field2 = $field2;
|
||||
}
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
if (! isset($data[$this->field2])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data[$this->field] === $data[$this->field2];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
use PDO;
|
||||
|
||||
class Exists extends Base
|
||||
{
|
||||
private $pdo;
|
||||
private $key;
|
||||
private $table;
|
||||
|
||||
public function __construct($field, $error_message, PDO $pdo, $table, $key = '')
|
||||
{
|
||||
parent::__construct($field, $error_message);
|
||||
|
||||
$this->pdo = $pdo;
|
||||
$this->table = $table;
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if (! $this->isFieldNotEmpty($data)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->key === '') {
|
||||
$this->key = $this->field;
|
||||
}
|
||||
|
||||
$rq = $this->pdo->prepare('SELECT 1 FROM '.$this->table.' WHERE '.$this->key.'=?');
|
||||
$rq->execute(array($data[$this->field]));
|
||||
|
||||
return $rq->fetchColumn() == 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class GreaterThan extends Base
|
||||
{
|
||||
private $min;
|
||||
|
||||
public function __construct($field, $error_message, $min)
|
||||
{
|
||||
parent::__construct($field, $error_message);
|
||||
$this->min = $min;
|
||||
}
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
return $data[$this->field] > $this->min;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class GreaterThanOrEqual extends Base
|
||||
{
|
||||
private $min;
|
||||
|
||||
public function __construct($field, $error_message, $min)
|
||||
{
|
||||
parent::__construct($field, $error_message);
|
||||
$this->min = $min;
|
||||
}
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
return $data[$this->field] >= $this->min;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class InArray extends Base
|
||||
{
|
||||
protected $array;
|
||||
|
||||
public function __construct($field, array $array, $error_message)
|
||||
{
|
||||
parent::__construct($field, $error_message);
|
||||
$this->array = $array;
|
||||
}
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
return in_array($data[$this->field], $this->array);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class Integer extends Base
|
||||
{
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
if (is_string($data[$this->field])) {
|
||||
|
||||
if ($data[$this->field][0] === '-') {
|
||||
return ctype_digit(substr($data[$this->field], 1));
|
||||
}
|
||||
|
||||
return ctype_digit((string) $data[$this->field]);
|
||||
}
|
||||
else {
|
||||
return is_int($data[$this->field]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class Ip extends Base
|
||||
{
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
return filter_var($data[$this->field], FILTER_VALIDATE_IP) !== false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class Length extends Base
|
||||
{
|
||||
private $min;
|
||||
private $max;
|
||||
|
||||
public function __construct($field, $error_message, $min, $max)
|
||||
{
|
||||
parent::__construct($field, $error_message);
|
||||
$this->min = $min;
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
$length = mb_strlen($data[$this->field], 'UTF-8');
|
||||
return $length >= $this->min && $length <= $this->max;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class MaxLength extends Base
|
||||
{
|
||||
private $max;
|
||||
|
||||
public function __construct($field, $error_message, $max)
|
||||
{
|
||||
parent::__construct($field, $error_message);
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
$length = mb_strlen($data[$this->field], 'UTF-8');
|
||||
return $length <= $this->max;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class MinLength extends Base
|
||||
{
|
||||
private $min;
|
||||
|
||||
public function __construct($field, $error_message, $min)
|
||||
{
|
||||
parent::__construct($field, $error_message);
|
||||
$this->min = $min;
|
||||
}
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
$length = mb_strlen($data[$this->field], 'UTF-8');
|
||||
return $length >= $this->min;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class NotEmpty extends Base
|
||||
{
|
||||
public function execute(array $data)
|
||||
{
|
||||
if (array_key_exists($this->field, $data)) {
|
||||
return $data[$this->field] !== null && $data[$this->field] !== '';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class NotEquals extends Base
|
||||
{
|
||||
private $field2;
|
||||
|
||||
public function __construct($field1, $field2, $error_message)
|
||||
{
|
||||
parent::__construct($field1, $error_message);
|
||||
$this->field2 = $field2;
|
||||
}
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
|
||||
if (! isset($data[$this->field2])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $data[$this->field] !== $data[$this->field2];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class NotInArray extends InArray
|
||||
{
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
return ! in_array($data[$this->field], $this->array);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class Numeric extends Base
|
||||
{
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
return is_numeric($data[$this->field]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class Range extends Base
|
||||
{
|
||||
private $min;
|
||||
private $max;
|
||||
|
||||
public function __construct($field, $error_message, $min, $max)
|
||||
{
|
||||
parent::__construct($field, $error_message);
|
||||
|
||||
$this->min = $min;
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
|
||||
if (! is_numeric($data[$this->field])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data[$this->field] < $this->min || $data[$this->field] > $this->max) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class Required extends Base
|
||||
{
|
||||
public function execute(array $data)
|
||||
{
|
||||
return $this->isFieldNotEmpty($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class Timezone extends Base
|
||||
{
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
return in_array($data[$this->field], timezone_identifiers_list());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
class URL extends Base
|
||||
{
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
return filter_var($data[$this->field], FILTER_VALIDATE_URL) !== false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleValidator\Validators;
|
||||
|
||||
use PDO;
|
||||
|
||||
class Unique extends Base
|
||||
{
|
||||
private $pdo;
|
||||
private $primary_key;
|
||||
private $table;
|
||||
|
||||
public function __construct($field, $error_message, PDO $pdo, $table, $primary_key = 'id')
|
||||
{
|
||||
parent::__construct($field, $error_message);
|
||||
|
||||
$this->pdo = $pdo;
|
||||
$this->primary_key = $primary_key;
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isFieldNotEmpty($data)) {
|
||||
if (! isset($data[$this->primary_key])) {
|
||||
$rq = $this->pdo->prepare('SELECT 1 FROM '.$this->table.' WHERE '.$this->field.'=?');
|
||||
$rq->execute(array($data[$this->field]));
|
||||
}
|
||||
else {
|
||||
|
||||
$rq = $this->pdo->prepare(
|
||||
'SELECT 1 FROM '.$this->table.'
|
||||
WHERE '.$this->field.'=? AND '.$this->primary_key.' != ?'
|
||||
);
|
||||
|
||||
$rq->execute(array($data[$this->field], $data[$this->primary_key]));
|
||||
}
|
||||
|
||||
$result = $rq->fetchColumn();
|
||||
|
||||
if ($result == 1) { // Postgresql returns an integer but other database returns a string '1'
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2018 Emanuil Rusev, erusev.com
|
||||
|
||||
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.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\EventDispatcher\Attribute;
|
||||
|
||||
/**
|
||||
* Service tag to autoconfigure event listeners.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
|
||||
class AsEventListener
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $event = null,
|
||||
public ?string $method = null,
|
||||
public int $priority = 0,
|
||||
public ?string $dispatcher = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.4
|
||||
---
|
||||
|
||||
* Allow `#[AsEventListener]` attribute on methods
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* Add `#[AsEventListener]` attribute for declaring listeners on PHP 8
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
||||
* The `LegacyEventDispatcherProxy` class has been deprecated.
|
||||
* Added an optional `dispatcher` attribute to the listener and subscriber tags in `RegisterListenerPass`.
|
||||
|
||||
5.0.0
|
||||
-----
|
||||
|
||||
* The signature of the `EventDispatcherInterface::dispatch()` method has been changed to `dispatch($event, string $eventName = null): object`.
|
||||
* The `Event` class has been removed in favor of `Symfony\Contracts\EventDispatcher\Event`.
|
||||
* The `TraceableEventDispatcherInterface` has been removed.
|
||||
* The `WrappedListener` class is now final.
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`.
|
||||
* Made the `event` attribute of the `kernel.event_listener` tag optional for FQCN events.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated
|
||||
* deprecated the `Event` class, use `Symfony\Contracts\EventDispatcher\Event` instead
|
||||
|
||||
4.1.0
|
||||
-----
|
||||
|
||||
* added support for invokable event listeners tagged with `kernel.event_listener` by default
|
||||
* The `TraceableEventDispatcher::getOrphanedEvents()` method has been added.
|
||||
* The `TraceableEventDispatcherInterface` has been deprecated.
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
||||
* removed the `ContainerAwareEventDispatcher` class
|
||||
* added the `reset()` method to the `TraceableEventDispatcherInterface`
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated.
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
* The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead.
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
* The method `getListenerPriority($eventName, $listener)` has been added to the
|
||||
`EventDispatcherInterface`.
|
||||
* The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()`
|
||||
and `Event::getName()` have been removed.
|
||||
The event dispatcher and the event name are passed to the listener call.
|
||||
|
||||
2.5.0
|
||||
-----
|
||||
|
||||
* added Debug\TraceableEventDispatcher (originally in HttpKernel)
|
||||
* changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface
|
||||
* added RegisterListenersPass (originally in HttpKernel)
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
* added TraceableEventDispatcherInterface
|
||||
* added ContainerAwareEventDispatcher
|
||||
* added a reference to the EventDispatcher on the Event
|
||||
* added a reference to the Event name on the event
|
||||
* added fluid interface to the dispatch() method which now returns the Event
|
||||
object
|
||||
* added GenericEvent event class
|
||||
* added the possibility for subscribers to subscribe several times for the
|
||||
same event
|
||||
* added ImmutableEventDispatcher
|
||||
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\EventDispatcher\Debug;
|
||||
|
||||
use Psr\EventDispatcher\StoppableEventInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* Collects some data about event listeners.
|
||||
*
|
||||
* This event dispatcher delegates the dispatching to another one.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface
|
||||
{
|
||||
protected $logger;
|
||||
protected $stopwatch;
|
||||
|
||||
/**
|
||||
* @var \SplObjectStorage<WrappedListener, array{string, string}>
|
||||
*/
|
||||
private $callStack;
|
||||
private $dispatcher;
|
||||
private $wrappedListeners;
|
||||
private $orphanedEvents;
|
||||
private $requestStack;
|
||||
private $currentRequestHash = '';
|
||||
|
||||
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, ?LoggerInterface $logger = null, ?RequestStack $requestStack = null)
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->stopwatch = $stopwatch;
|
||||
$this->logger = $logger;
|
||||
$this->wrappedListeners = [];
|
||||
$this->orphanedEvents = [];
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addListener(string $eventName, $listener, int $priority = 0)
|
||||
{
|
||||
$this->dispatcher->addListener($eventName, $listener, $priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addSubscriber(EventSubscriberInterface $subscriber)
|
||||
{
|
||||
$this->dispatcher->addSubscriber($subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeListener(string $eventName, $listener)
|
||||
{
|
||||
if (isset($this->wrappedListeners[$eventName])) {
|
||||
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
|
||||
if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
|
||||
$listener = $wrappedListener;
|
||||
unset($this->wrappedListeners[$eventName][$index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->dispatcher->removeListener($eventName, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeSubscriber(EventSubscriberInterface $subscriber)
|
||||
{
|
||||
return $this->dispatcher->removeSubscriber($subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getListeners(?string $eventName = null)
|
||||
{
|
||||
return $this->dispatcher->getListeners($eventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getListenerPriority(string $eventName, $listener)
|
||||
{
|
||||
// we might have wrapped listeners for the event (if called while dispatching)
|
||||
// in that case get the priority by wrapper
|
||||
if (isset($this->wrappedListeners[$eventName])) {
|
||||
foreach ($this->wrappedListeners[$eventName] as $wrappedListener) {
|
||||
if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
|
||||
return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->dispatcher->getListenerPriority($eventName, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasListeners(?string $eventName = null)
|
||||
{
|
||||
return $this->dispatcher->hasListeners($eventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dispatch(object $event, ?string $eventName = null): object
|
||||
{
|
||||
$eventName = $eventName ?? \get_class($event);
|
||||
|
||||
if (null === $this->callStack) {
|
||||
$this->callStack = new \SplObjectStorage();
|
||||
}
|
||||
|
||||
$currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';
|
||||
|
||||
if (null !== $this->logger && $event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
|
||||
$this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
|
||||
}
|
||||
|
||||
$this->preProcess($eventName);
|
||||
try {
|
||||
$this->beforeDispatch($eventName, $event);
|
||||
try {
|
||||
$e = $this->stopwatch->start($eventName, 'section');
|
||||
try {
|
||||
$this->dispatcher->dispatch($event, $eventName);
|
||||
} finally {
|
||||
if ($e->isStarted()) {
|
||||
$e->stop();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$this->afterDispatch($eventName, $event);
|
||||
}
|
||||
} finally {
|
||||
$this->currentRequestHash = $currentRequestHash;
|
||||
$this->postProcess($eventName);
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCalledListeners(?Request $request = null)
|
||||
{
|
||||
if (null === $this->callStack) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$hash = $request ? spl_object_hash($request) : null;
|
||||
$called = [];
|
||||
foreach ($this->callStack as $listener) {
|
||||
[$eventName, $requestHash] = $this->callStack->getInfo();
|
||||
if (null === $hash || $hash === $requestHash) {
|
||||
$called[] = $listener->getInfo($eventName);
|
||||
}
|
||||
}
|
||||
|
||||
return $called;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getNotCalledListeners(?Request $request = null)
|
||||
{
|
||||
try {
|
||||
$allListeners = $this->getListeners();
|
||||
} catch (\Exception $e) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);
|
||||
}
|
||||
|
||||
// unable to retrieve the uncalled listeners
|
||||
return [];
|
||||
}
|
||||
|
||||
$hash = $request ? spl_object_hash($request) : null;
|
||||
$calledListeners = [];
|
||||
|
||||
if (null !== $this->callStack) {
|
||||
foreach ($this->callStack as $calledListener) {
|
||||
[, $requestHash] = $this->callStack->getInfo();
|
||||
|
||||
if (null === $hash || $hash === $requestHash) {
|
||||
$calledListeners[] = $calledListener->getWrappedListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$notCalled = [];
|
||||
foreach ($allListeners as $eventName => $listeners) {
|
||||
foreach ($listeners as $listener) {
|
||||
if (!\in_array($listener, $calledListeners, true)) {
|
||||
if (!$listener instanceof WrappedListener) {
|
||||
$listener = new WrappedListener($listener, null, $this->stopwatch, $this);
|
||||
}
|
||||
$notCalled[] = $listener->getInfo($eventName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uasort($notCalled, [$this, 'sortNotCalledListeners']);
|
||||
|
||||
return $notCalled;
|
||||
}
|
||||
|
||||
public function getOrphanedEvents(?Request $request = null): array
|
||||
{
|
||||
if ($request) {
|
||||
return $this->orphanedEvents[spl_object_hash($request)] ?? [];
|
||||
}
|
||||
|
||||
if (!$this->orphanedEvents) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_merge(...array_values($this->orphanedEvents));
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->callStack = null;
|
||||
$this->orphanedEvents = [];
|
||||
$this->currentRequestHash = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies all method calls to the original event dispatcher.
|
||||
*
|
||||
* @param string $method The method name
|
||||
* @param array $arguments The method arguments
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $method, array $arguments)
|
||||
{
|
||||
return $this->dispatcher->{$method}(...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before dispatching the event.
|
||||
*/
|
||||
protected function beforeDispatch(string $eventName, object $event)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after dispatching the event.
|
||||
*/
|
||||
protected function afterDispatch(string $eventName, object $event)
|
||||
{
|
||||
}
|
||||
|
||||
private function preProcess(string $eventName): void
|
||||
{
|
||||
if (!$this->dispatcher->hasListeners($eventName)) {
|
||||
$this->orphanedEvents[$this->currentRequestHash][] = $eventName;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
|
||||
$priority = $this->getListenerPriority($eventName, $listener);
|
||||
$wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this);
|
||||
$this->wrappedListeners[$eventName][] = $wrappedListener;
|
||||
$this->dispatcher->removeListener($eventName, $listener);
|
||||
$this->dispatcher->addListener($eventName, $wrappedListener, $priority);
|
||||
$this->callStack->offsetSet($wrappedListener, [$eventName, $this->currentRequestHash]);
|
||||
}
|
||||
}
|
||||
|
||||
private function postProcess(string $eventName): void
|
||||
{
|
||||
unset($this->wrappedListeners[$eventName]);
|
||||
$skipped = false;
|
||||
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
|
||||
if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
|
||||
continue;
|
||||
}
|
||||
// Unwrap listener
|
||||
$priority = $this->getListenerPriority($eventName, $listener);
|
||||
$this->dispatcher->removeListener($eventName, $listener);
|
||||
$this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$context = ['event' => $eventName, 'listener' => $listener->getPretty()];
|
||||
}
|
||||
|
||||
if ($listener->wasCalled()) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
|
||||
}
|
||||
} else {
|
||||
$this->callStack->detach($listener);
|
||||
}
|
||||
|
||||
if (null !== $this->logger && $skipped) {
|
||||
$this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
|
||||
}
|
||||
|
||||
if ($listener->stoppedPropagation()) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
|
||||
}
|
||||
|
||||
$skipped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function sortNotCalledListeners(array $a, array $b)
|
||||
{
|
||||
if (0 !== $cmp = strcmp($a['event'], $b['event'])) {
|
||||
return $cmp;
|
||||
}
|
||||
|
||||
if (\is_int($a['priority']) && !\is_int($b['priority'])) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!\is_int($a['priority']) && \is_int($b['priority'])) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ($a['priority'] === $b['priority']) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($a['priority'] > $b['priority']) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\EventDispatcher\Debug;
|
||||
|
||||
use Psr\EventDispatcher\StoppableEventInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
use Symfony\Component\VarDumper\Caster\ClassStub;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class WrappedListener
|
||||
{
|
||||
private $listener;
|
||||
private $optimizedListener;
|
||||
private $name;
|
||||
private $called;
|
||||
private $stoppedPropagation;
|
||||
private $stopwatch;
|
||||
private $dispatcher;
|
||||
private $pretty;
|
||||
private $stub;
|
||||
private $priority;
|
||||
private static $hasClassStub;
|
||||
|
||||
public function __construct($listener, ?string $name, Stopwatch $stopwatch, ?EventDispatcherInterface $dispatcher = null)
|
||||
{
|
||||
$this->listener = $listener;
|
||||
$this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null);
|
||||
$this->stopwatch = $stopwatch;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->called = false;
|
||||
$this->stoppedPropagation = false;
|
||||
|
||||
if (\is_array($listener)) {
|
||||
$this->name = \is_object($listener[0]) ? get_debug_type($listener[0]) : $listener[0];
|
||||
$this->pretty = $this->name.'::'.$listener[1];
|
||||
} elseif ($listener instanceof \Closure) {
|
||||
$r = new \ReflectionFunction($listener);
|
||||
if (str_contains($r->name, '{closure}')) {
|
||||
$this->pretty = $this->name = 'closure';
|
||||
} elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) {
|
||||
$this->name = $class->name;
|
||||
$this->pretty = $this->name.'::'.$r->name;
|
||||
} else {
|
||||
$this->pretty = $this->name = $r->name;
|
||||
}
|
||||
} elseif (\is_string($listener)) {
|
||||
$this->pretty = $this->name = $listener;
|
||||
} else {
|
||||
$this->name = get_debug_type($listener);
|
||||
$this->pretty = $this->name.'::__invoke';
|
||||
}
|
||||
|
||||
if (null !== $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
if (null === self::$hasClassStub) {
|
||||
self::$hasClassStub = class_exists(ClassStub::class);
|
||||
}
|
||||
}
|
||||
|
||||
public function getWrappedListener()
|
||||
{
|
||||
return $this->listener;
|
||||
}
|
||||
|
||||
public function wasCalled(): bool
|
||||
{
|
||||
return $this->called;
|
||||
}
|
||||
|
||||
public function stoppedPropagation(): bool
|
||||
{
|
||||
return $this->stoppedPropagation;
|
||||
}
|
||||
|
||||
public function getPretty(): string
|
||||
{
|
||||
return $this->pretty;
|
||||
}
|
||||
|
||||
public function getInfo(string $eventName): array
|
||||
{
|
||||
if (null === $this->stub) {
|
||||
$this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()';
|
||||
}
|
||||
|
||||
return [
|
||||
'event' => $eventName,
|
||||
'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null),
|
||||
'pretty' => $this->pretty,
|
||||
'stub' => $this->stub,
|
||||
];
|
||||
}
|
||||
|
||||
public function __invoke(object $event, string $eventName, EventDispatcherInterface $dispatcher): void
|
||||
{
|
||||
$dispatcher = $this->dispatcher ?: $dispatcher;
|
||||
|
||||
$this->called = true;
|
||||
$this->priority = $dispatcher->getListenerPriority($eventName, $this->listener);
|
||||
|
||||
$e = $this->stopwatch->start($this->name, 'event_listener');
|
||||
|
||||
($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
|
||||
|
||||
if ($e->isStarted()) {
|
||||
$e->stop();
|
||||
}
|
||||
|
||||
if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
|
||||
$this->stoppedPropagation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\EventDispatcher\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* This pass allows bundles to extend the list of event aliases.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*/
|
||||
class AddEventAliasesPass implements CompilerPassInterface
|
||||
{
|
||||
private $eventAliases;
|
||||
private $eventAliasesParameter;
|
||||
|
||||
public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases')
|
||||
{
|
||||
if (1 < \func_num_args()) {
|
||||
trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
|
||||
}
|
||||
|
||||
$this->eventAliases = $eventAliases;
|
||||
$this->eventAliasesParameter = $eventAliasesParameter;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : [];
|
||||
|
||||
$container->setParameter(
|
||||
$this->eventAliasesParameter,
|
||||
array_merge($eventAliases, $this->eventAliases)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\EventDispatcher\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Compiler pass to register tagged services for an event dispatcher.
|
||||
*/
|
||||
class RegisterListenersPass implements CompilerPassInterface
|
||||
{
|
||||
protected $dispatcherService;
|
||||
protected $listenerTag;
|
||||
protected $subscriberTag;
|
||||
protected $eventAliasesParameter;
|
||||
|
||||
private $hotPathEvents = [];
|
||||
private $hotPathTagName = 'container.hot_path';
|
||||
private $noPreloadEvents = [];
|
||||
private $noPreloadTagName = 'container.no_preload';
|
||||
|
||||
public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases')
|
||||
{
|
||||
if (0 < \func_num_args()) {
|
||||
trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
|
||||
}
|
||||
|
||||
$this->dispatcherService = $dispatcherService;
|
||||
$this->listenerTag = $listenerTag;
|
||||
$this->subscriberTag = $subscriberTag;
|
||||
$this->eventAliasesParameter = $eventAliasesParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setHotPathEvents(array $hotPathEvents)
|
||||
{
|
||||
$this->hotPathEvents = array_flip($hotPathEvents);
|
||||
|
||||
if (1 < \func_num_args()) {
|
||||
trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__);
|
||||
$this->hotPathTagName = func_get_arg(1);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setNoPreloadEvents(array $noPreloadEvents): self
|
||||
{
|
||||
$this->noPreloadEvents = array_flip($noPreloadEvents);
|
||||
|
||||
if (1 < \func_num_args()) {
|
||||
trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__);
|
||||
$this->noPreloadTagName = func_get_arg(1);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aliases = [];
|
||||
|
||||
if ($container->hasParameter($this->eventAliasesParameter)) {
|
||||
$aliases = $container->getParameter($this->eventAliasesParameter);
|
||||
}
|
||||
|
||||
$globalDispatcherDefinition = $container->findDefinition($this->dispatcherService);
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) {
|
||||
$noPreload = 0;
|
||||
|
||||
foreach ($events as $event) {
|
||||
$priority = $event['priority'] ?? 0;
|
||||
|
||||
if (!isset($event['event'])) {
|
||||
if ($container->getDefinition($id)->hasTag($this->subscriberTag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$event['method'] = $event['method'] ?? '__invoke';
|
||||
$event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
|
||||
}
|
||||
|
||||
$event['event'] = $aliases[$event['event']] ?? $event['event'];
|
||||
|
||||
if (!isset($event['method'])) {
|
||||
$event['method'] = 'on'.preg_replace_callback([
|
||||
'/(?<=\b|_)[a-z]/i',
|
||||
'/[^a-z0-9]/i',
|
||||
], function ($matches) { return strtoupper($matches[0]); }, $event['event']);
|
||||
$event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
|
||||
|
||||
if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) {
|
||||
$event['method'] = '__invoke';
|
||||
}
|
||||
}
|
||||
|
||||
$dispatcherDefinition = $globalDispatcherDefinition;
|
||||
if (isset($event['dispatcher'])) {
|
||||
$dispatcherDefinition = $container->getDefinition($event['dispatcher']);
|
||||
}
|
||||
|
||||
$dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
|
||||
|
||||
if (isset($this->hotPathEvents[$event['event']])) {
|
||||
$container->getDefinition($id)->addTag($this->hotPathTagName);
|
||||
} elseif (isset($this->noPreloadEvents[$event['event']])) {
|
||||
++$noPreload;
|
||||
}
|
||||
}
|
||||
|
||||
if ($noPreload && \count($events) === $noPreload) {
|
||||
$container->getDefinition($id)->addTag($this->noPreloadTagName);
|
||||
}
|
||||
}
|
||||
|
||||
$extractingDispatcher = new ExtractingEventDispatcher();
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $tags) {
|
||||
$def = $container->getDefinition($id);
|
||||
|
||||
// We must assume that the class value has been correctly filled, even if the service is created by a factory
|
||||
$class = $def->getClass();
|
||||
|
||||
if (!$r = $container->getReflectionClass($class)) {
|
||||
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
|
||||
}
|
||||
if (!$r->isSubclassOf(EventSubscriberInterface::class)) {
|
||||
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class));
|
||||
}
|
||||
$class = $r->name;
|
||||
|
||||
$dispatcherDefinitions = [];
|
||||
foreach ($tags as $attributes) {
|
||||
if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dispatcherDefinitions[$attributes['dispatcher']] = $container->getDefinition($attributes['dispatcher']);
|
||||
}
|
||||
|
||||
if (!$dispatcherDefinitions) {
|
||||
$dispatcherDefinitions = [$globalDispatcherDefinition];
|
||||
}
|
||||
|
||||
$noPreload = 0;
|
||||
ExtractingEventDispatcher::$aliases = $aliases;
|
||||
ExtractingEventDispatcher::$subscriber = $class;
|
||||
$extractingDispatcher->addSubscriber($extractingDispatcher);
|
||||
foreach ($extractingDispatcher->listeners as $args) {
|
||||
$args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]];
|
||||
foreach ($dispatcherDefinitions as $dispatcherDefinition) {
|
||||
$dispatcherDefinition->addMethodCall('addListener', $args);
|
||||
}
|
||||
|
||||
if (isset($this->hotPathEvents[$args[0]])) {
|
||||
$container->getDefinition($id)->addTag($this->hotPathTagName);
|
||||
} elseif (isset($this->noPreloadEvents[$args[0]])) {
|
||||
++$noPreload;
|
||||
}
|
||||
}
|
||||
if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) {
|
||||
$container->getDefinition($id)->addTag($this->noPreloadTagName);
|
||||
}
|
||||
$extractingDispatcher->listeners = [];
|
||||
ExtractingEventDispatcher::$aliases = [];
|
||||
}
|
||||
}
|
||||
|
||||
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string
|
||||
{
|
||||
if (
|
||||
null === ($class = $container->getDefinition($id)->getClass())
|
||||
|| !($r = $container->getReflectionClass($class, false))
|
||||
|| !$r->hasMethod($method)
|
||||
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
|
||||
|| !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
|
||||
|| $type->isBuiltin()
|
||||
|| Event::class === ($name = $type->getName())
|
||||
) {
|
||||
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
|
||||
{
|
||||
public $listeners = [];
|
||||
|
||||
public static $aliases = [];
|
||||
public static $subscriber;
|
||||
|
||||
public function addListener(string $eventName, $listener, int $priority = 0)
|
||||
{
|
||||
$this->listeners[] = [$eventName, $listener[1], $priority];
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
$events = [];
|
||||
|
||||
foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) {
|
||||
$events[self::$aliases[$eventName] ?? $eventName] = $params;
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\EventDispatcher;
|
||||
|
||||
use Psr\EventDispatcher\StoppableEventInterface;
|
||||
use Symfony\Component\EventDispatcher\Debug\WrappedListener;
|
||||
|
||||
/**
|
||||
* The EventDispatcherInterface is the central point of Symfony's event listener system.
|
||||
*
|
||||
* Listeners are registered on the manager and events are dispatched through the
|
||||
* manager.
|
||||
*
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @author Jordan Alliot <jordan.alliot@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class EventDispatcher implements EventDispatcherInterface
|
||||
{
|
||||
private $listeners = [];
|
||||
private $sorted = [];
|
||||
private $optimized;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (__CLASS__ === static::class) {
|
||||
$this->optimized = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dispatch(object $event, ?string $eventName = null): object
|
||||
{
|
||||
$eventName = $eventName ?? \get_class($event);
|
||||
|
||||
if (null !== $this->optimized) {
|
||||
$listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
|
||||
} else {
|
||||
$listeners = $this->getListeners($eventName);
|
||||
}
|
||||
|
||||
if ($listeners) {
|
||||
$this->callListeners($listeners, $eventName, $event);
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getListeners(?string $eventName = null)
|
||||
{
|
||||
if (null !== $eventName) {
|
||||
if (empty($this->listeners[$eventName])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isset($this->sorted[$eventName])) {
|
||||
$this->sortListeners($eventName);
|
||||
}
|
||||
|
||||
return $this->sorted[$eventName];
|
||||
}
|
||||
|
||||
foreach ($this->listeners as $eventName => $eventListeners) {
|
||||
if (!isset($this->sorted[$eventName])) {
|
||||
$this->sortListeners($eventName);
|
||||
}
|
||||
}
|
||||
|
||||
return array_filter($this->sorted);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getListenerPriority(string $eventName, $listener)
|
||||
{
|
||||
if (empty($this->listeners[$eventName])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
|
||||
$listener[0] = $listener[0]();
|
||||
$listener[1] = $listener[1] ?? '__invoke';
|
||||
}
|
||||
|
||||
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
|
||||
foreach ($listeners as &$v) {
|
||||
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
|
||||
$v[0] = $v[0]();
|
||||
$v[1] = $v[1] ?? '__invoke';
|
||||
}
|
||||
if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
|
||||
return $priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasListeners(?string $eventName = null)
|
||||
{
|
||||
if (null !== $eventName) {
|
||||
return !empty($this->listeners[$eventName]);
|
||||
}
|
||||
|
||||
foreach ($this->listeners as $eventListeners) {
|
||||
if ($eventListeners) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addListener(string $eventName, $listener, int $priority = 0)
|
||||
{
|
||||
$this->listeners[$eventName][$priority][] = $listener;
|
||||
unset($this->sorted[$eventName], $this->optimized[$eventName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeListener(string $eventName, $listener)
|
||||
{
|
||||
if (empty($this->listeners[$eventName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
|
||||
$listener[0] = $listener[0]();
|
||||
$listener[1] = $listener[1] ?? '__invoke';
|
||||
}
|
||||
|
||||
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
|
||||
foreach ($listeners as $k => &$v) {
|
||||
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
|
||||
$v[0] = $v[0]();
|
||||
$v[1] = $v[1] ?? '__invoke';
|
||||
}
|
||||
if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
|
||||
unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$listeners) {
|
||||
unset($this->listeners[$eventName][$priority]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addSubscriber(EventSubscriberInterface $subscriber)
|
||||
{
|
||||
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
|
||||
if (\is_string($params)) {
|
||||
$this->addListener($eventName, [$subscriber, $params]);
|
||||
} elseif (\is_string($params[0])) {
|
||||
$this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0);
|
||||
} else {
|
||||
foreach ($params as $listener) {
|
||||
$this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeSubscriber(EventSubscriberInterface $subscriber)
|
||||
{
|
||||
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
|
||||
if (\is_array($params) && \is_array($params[0])) {
|
||||
foreach ($params as $listener) {
|
||||
$this->removeListener($eventName, [$subscriber, $listener[0]]);
|
||||
}
|
||||
} else {
|
||||
$this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the listeners of an event.
|
||||
*
|
||||
* This method can be overridden to add functionality that is executed
|
||||
* for each listener.
|
||||
*
|
||||
* @param callable[] $listeners The event listeners
|
||||
* @param string $eventName The name of the event to dispatch
|
||||
* @param object $event The event object to pass to the event handlers/listeners
|
||||
*/
|
||||
protected function callListeners(iterable $listeners, string $eventName, object $event)
|
||||
{
|
||||
$stoppable = $event instanceof StoppableEventInterface;
|
||||
|
||||
foreach ($listeners as $listener) {
|
||||
if ($stoppable && $event->isPropagationStopped()) {
|
||||
break;
|
||||
}
|
||||
$listener($event, $eventName, $this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the internal list of listeners for the given event by priority.
|
||||
*/
|
||||
private function sortListeners(string $eventName)
|
||||
{
|
||||
krsort($this->listeners[$eventName]);
|
||||
$this->sorted[$eventName] = [];
|
||||
|
||||
foreach ($this->listeners[$eventName] as &$listeners) {
|
||||
foreach ($listeners as $k => &$listener) {
|
||||
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
|
||||
$listener[0] = $listener[0]();
|
||||
$listener[1] = $listener[1] ?? '__invoke';
|
||||
}
|
||||
$this->sorted[$eventName][] = $listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimizes the internal list of listeners for the given event by priority.
|
||||
*/
|
||||
private function optimizeListeners(string $eventName): array
|
||||
{
|
||||
krsort($this->listeners[$eventName]);
|
||||
$this->optimized[$eventName] = [];
|
||||
|
||||
foreach ($this->listeners[$eventName] as &$listeners) {
|
||||
foreach ($listeners as &$listener) {
|
||||
$closure = &$this->optimized[$eventName][];
|
||||
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
|
||||
$closure = static function (...$args) use (&$listener, &$closure) {
|
||||
if ($listener[0] instanceof \Closure) {
|
||||
$listener[0] = $listener[0]();
|
||||
$listener[1] = $listener[1] ?? '__invoke';
|
||||
}
|
||||
($closure = \Closure::fromCallable($listener))(...$args);
|
||||
};
|
||||
} else {
|
||||
$closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->optimized[$eventName];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\EventDispatcher;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* The EventDispatcherInterface is the central point of Symfony's event listener system.
|
||||
* Listeners are registered on the manager and events are dispatched through the
|
||||
* manager.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
interface EventDispatcherInterface extends ContractsEventDispatcherInterface
|
||||
{
|
||||
/**
|
||||
* Adds an event listener that listens on the specified events.
|
||||
*
|
||||
* @param int $priority The higher this value, the earlier an event
|
||||
* listener will be triggered in the chain (defaults to 0)
|
||||
*/
|
||||
public function addListener(string $eventName, callable $listener, int $priority = 0);
|
||||
|
||||
/**
|
||||
* Adds an event subscriber.
|
||||
*
|
||||
* The subscriber is asked for all the events it is
|
||||
* interested in and added as a listener for these events.
|
||||
*/
|
||||
public function addSubscriber(EventSubscriberInterface $subscriber);
|
||||
|
||||
/**
|
||||
* Removes an event listener from the specified events.
|
||||
*/
|
||||
public function removeListener(string $eventName, callable $listener);
|
||||
|
||||
public function removeSubscriber(EventSubscriberInterface $subscriber);
|
||||
|
||||
/**
|
||||
* Gets the listeners of a specific event or all listeners sorted by descending priority.
|
||||
*
|
||||
* @return array<callable[]|callable>
|
||||
*/
|
||||
public function getListeners(?string $eventName = null);
|
||||
|
||||
/**
|
||||
* Gets the listener priority for a specific event.
|
||||
*
|
||||
* Returns null if the event or the listener does not exist.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getListenerPriority(string $eventName, callable $listener);
|
||||
|
||||
/**
|
||||
* Checks whether an event has any registered listeners.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasListeners(?string $eventName = null);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\EventDispatcher;
|
||||
|
||||
/**
|
||||
* An EventSubscriber knows itself what events it is interested in.
|
||||
* If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
|
||||
* {@link getSubscribedEvents} and registers the subscriber as a listener for all
|
||||
* returned events.
|
||||
*
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
interface EventSubscriberInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array of event names this subscriber wants to listen to.
|
||||
*
|
||||
* The array keys are event names and the value can be:
|
||||
*
|
||||
* * The method name to call (priority defaults to 0)
|
||||
* * An array composed of the method name to call and the priority
|
||||
* * An array of arrays composed of the method names to call and respective
|
||||
* priorities, or 0 if unset
|
||||
*
|
||||
* For instance:
|
||||
*
|
||||
* * ['eventName' => 'methodName']
|
||||
* * ['eventName' => ['methodName', $priority]]
|
||||
* * ['eventName' => [['methodName1', $priority], ['methodName2']]]
|
||||
*
|
||||
* The code must not depend on runtime state as it will only be called at compile time.
|
||||
* All logic depending on runtime state must be put into the individual methods handling the events.
|
||||
*
|
||||
* @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
|
||||
*/
|
||||
public static function getSubscribedEvents();
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\EventDispatcher;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event encapsulation class.
|
||||
*
|
||||
* Encapsulates events thus decoupling the observer from the subject they encapsulate.
|
||||
*
|
||||
* @author Drak <drak@zikula.org>
|
||||
*
|
||||
* @implements \ArrayAccess<string, mixed>
|
||||
* @implements \IteratorAggregate<string, mixed>
|
||||
*/
|
||||
class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
|
||||
{
|
||||
protected $subject;
|
||||
protected $arguments;
|
||||
|
||||
/**
|
||||
* Encapsulate an event with $subject and $args.
|
||||
*
|
||||
* @param mixed $subject The subject of the event, usually an object or a callable
|
||||
* @param array $arguments Arguments to store in the event
|
||||
*/
|
||||
public function __construct($subject = null, array $arguments = [])
|
||||
{
|
||||
$this->subject = $subject;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for subject property.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSubject()
|
||||
{
|
||||
return $this->subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get argument by key.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \InvalidArgumentException if key is not found
|
||||
*/
|
||||
public function getArgument(string $key)
|
||||
{
|
||||
if ($this->hasArgument($key)) {
|
||||
return $this->arguments[$key];
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add argument to event.
|
||||
*
|
||||
* @param mixed $value Value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setArgument(string $key, $value)
|
||||
{
|
||||
$this->arguments[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for all arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getArguments()
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set args property.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setArguments(array $args = [])
|
||||
{
|
||||
$this->arguments = $args;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has argument.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasArgument(string $key)
|
||||
{
|
||||
return \array_key_exists($key, $this->arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* ArrayAccess for argument getter.
|
||||
*
|
||||
* @param string $key Array key
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \InvalidArgumentException if key does not exist in $this->args
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($key)
|
||||
{
|
||||
return $this->getArgument($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* ArrayAccess for argument setter.
|
||||
*
|
||||
* @param string $key Array key to set
|
||||
* @param mixed $value Value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($key, $value)
|
||||
{
|
||||
$this->setArgument($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* ArrayAccess for unset argument.
|
||||
*
|
||||
* @param string $key Array key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($key)
|
||||
{
|
||||
if ($this->hasArgument($key)) {
|
||||
unset($this->arguments[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ArrayAccess has argument.
|
||||
*
|
||||
* @param string $key Array key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($key)
|
||||
{
|
||||
return $this->hasArgument($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* IteratorAggregate for iterating over the object like an array.
|
||||
*
|
||||
* @return \ArrayIterator<string, mixed>
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\EventDispatcher;
|
||||
|
||||
/**
|
||||
* A read-only proxy for an event dispatcher.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ImmutableEventDispatcher implements EventDispatcherInterface
|
||||
{
|
||||
private $dispatcher;
|
||||
|
||||
public function __construct(EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dispatch(object $event, ?string $eventName = null): object
|
||||
{
|
||||
return $this->dispatcher->dispatch($event, $eventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addListener(string $eventName, $listener, int $priority = 0)
|
||||
{
|
||||
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addSubscriber(EventSubscriberInterface $subscriber)
|
||||
{
|
||||
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeListener(string $eventName, $listener)
|
||||
{
|
||||
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeSubscriber(EventSubscriberInterface $subscriber)
|
||||
{
|
||||
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getListeners(?string $eventName = null)
|
||||
{
|
||||
return $this->dispatcher->getListeners($eventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getListenerPriority(string $eventName, $listener)
|
||||
{
|
||||
return $this->dispatcher->getListenerPriority($eventName, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasListeners(?string $eventName = null)
|
||||
{
|
||||
return $this->dispatcher->hasListeners($eventName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-present Fabien Potencier
|
||||
|
||||
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,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\EventDispatcher;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
trigger_deprecation('symfony/event-dispatcher', '5.1', '%s is deprecated, use the event dispatcher without the proxy.', LegacyEventDispatcherProxy::class);
|
||||
|
||||
/**
|
||||
* A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch().
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @deprecated since Symfony 5.1
|
||||
*/
|
||||
final class LegacyEventDispatcherProxy
|
||||
{
|
||||
public static function decorate(?EventDispatcherInterface $dispatcher): ?EventDispatcherInterface
|
||||
{
|
||||
return $dispatcher;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal;
|
||||
|
||||
use Eluceo\iCal\Util\ComponentUtil;
|
||||
|
||||
/**
|
||||
* Abstract Calender Component.
|
||||
*/
|
||||
abstract class Component
|
||||
{
|
||||
/**
|
||||
* Array of Components.
|
||||
*
|
||||
* @var Component[]
|
||||
*/
|
||||
protected $components = [];
|
||||
|
||||
/**
|
||||
* The order in which the components will be rendered during build.
|
||||
*
|
||||
* Not defined components will be appended at the end.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $componentsBuildOrder = ['VTIMEZONE', 'DAYLIGHT', 'STANDARD'];
|
||||
|
||||
/**
|
||||
* The type of the concrete Component.
|
||||
*
|
||||
* @abstract
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getType();
|
||||
|
||||
/**
|
||||
* Building the PropertyBag.
|
||||
*
|
||||
* @abstract
|
||||
*
|
||||
* @return PropertyBag
|
||||
*/
|
||||
abstract public function buildPropertyBag();
|
||||
|
||||
/**
|
||||
* Adds a Component.
|
||||
*
|
||||
* If $key is given, the component at $key will be replaced else the component will be append.
|
||||
*
|
||||
* @param Component $component The Component that will be added
|
||||
* @param null $key The key of the Component
|
||||
*/
|
||||
public function addComponent(self $component, $key = null)
|
||||
{
|
||||
if (null == $key) {
|
||||
$this->components[] = $component;
|
||||
} else {
|
||||
$this->components[$key] = $component;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all Components.
|
||||
*
|
||||
* @param Component[] $components The array of Component that will be set
|
||||
* @param null $key The key of the Component
|
||||
*/
|
||||
public function setComponents(array $components)
|
||||
{
|
||||
$this->components = $components;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an array containing the lines of the iCal file.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$lines = [];
|
||||
|
||||
$lines[] = sprintf('BEGIN:%s', $this->getType());
|
||||
|
||||
/** @var $property Property */
|
||||
foreach ($this->buildPropertyBag() as $property) {
|
||||
foreach ($property->toLines() as $l) {
|
||||
$lines[] = $l;
|
||||
}
|
||||
}
|
||||
|
||||
$this->buildComponents($lines);
|
||||
|
||||
$lines[] = sprintf('END:%s', $this->getType());
|
||||
|
||||
$ret = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
foreach (ComponentUtil::fold($line) as $l) {
|
||||
$ret[] = $l;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return implode("\r\n", $this->build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the output when treating the class as a string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $lines
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function buildComponents(array &$lines)
|
||||
{
|
||||
$componentsByType = [];
|
||||
|
||||
/** @var $component Component */
|
||||
foreach ($this->components as $component) {
|
||||
$type = $component->getType();
|
||||
if (!isset($componentsByType[$type])) {
|
||||
$componentsByType[$type] = [];
|
||||
}
|
||||
$componentsByType[$type][] = $component;
|
||||
}
|
||||
|
||||
// render ordered components
|
||||
foreach ($this->componentsBuildOrder as $type) {
|
||||
if (!isset($componentsByType[$type])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($componentsByType[$type] as $component) {
|
||||
$this->addComponentLines($lines, $component);
|
||||
}
|
||||
unset($componentsByType[$type]);
|
||||
}
|
||||
|
||||
// render all other
|
||||
foreach ($componentsByType as $components) {
|
||||
foreach ($components as $component) {
|
||||
$this->addComponentLines($lines, $component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Component $component
|
||||
*/
|
||||
private function addComponentLines(array &$lines, self $component)
|
||||
{
|
||||
foreach ($component->build() as $l) {
|
||||
$lines[] = $l;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Component;
|
||||
|
||||
use Eluceo\iCal\Component;
|
||||
use Eluceo\iCal\PropertyBag;
|
||||
|
||||
/**
|
||||
* Implementation of the VALARM component.
|
||||
*/
|
||||
class Alarm extends Component
|
||||
{
|
||||
/**
|
||||
* Alarm ACTION property.
|
||||
*
|
||||
* According to RFC 5545: 3.8.6.1. Action
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc5545#section-3.8.6.1
|
||||
*/
|
||||
const ACTION_AUDIO = 'AUDIO';
|
||||
const ACTION_DISPLAY = 'DISPLAY';
|
||||
const ACTION_EMAIL = 'EMAIL';
|
||||
|
||||
protected $action;
|
||||
protected $repeat;
|
||||
protected $duration;
|
||||
protected $description;
|
||||
protected $attendee;
|
||||
protected $trigger;
|
||||
|
||||
public function getType()
|
||||
{
|
||||
return 'VALARM';
|
||||
}
|
||||
|
||||
public function getAction()
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
public function getRepeat()
|
||||
{
|
||||
return $this->repeat;
|
||||
}
|
||||
|
||||
public function getDuration()
|
||||
{
|
||||
return $this->duration;
|
||||
}
|
||||
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getAttendee()
|
||||
{
|
||||
return $this->attendee;
|
||||
}
|
||||
|
||||
public function getTrigger()
|
||||
{
|
||||
return $this->trigger;
|
||||
}
|
||||
|
||||
public function setAction($action)
|
||||
{
|
||||
$this->action = $action;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setRepeat($repeat)
|
||||
{
|
||||
$this->repeat = $repeat;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDuration($duration)
|
||||
{
|
||||
$this->duration = $duration;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAttendee($attendee)
|
||||
{
|
||||
$this->attendee = $attendee;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTrigger($trigger)
|
||||
{
|
||||
$this->trigger = $trigger;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildPropertyBag()
|
||||
{
|
||||
$propertyBag = new PropertyBag();
|
||||
|
||||
if (null != $this->trigger) {
|
||||
$propertyBag->set('TRIGGER', $this->trigger);
|
||||
}
|
||||
|
||||
if (null != $this->action) {
|
||||
$propertyBag->set('ACTION', $this->action);
|
||||
}
|
||||
|
||||
if (null != $this->repeat) {
|
||||
$propertyBag->set('REPEAT', $this->repeat);
|
||||
}
|
||||
|
||||
if (null != $this->duration) {
|
||||
$propertyBag->set('DURATION', $this->duration);
|
||||
}
|
||||
|
||||
if (null != $this->description) {
|
||||
$propertyBag->set('DESCRIPTION', $this->description);
|
||||
}
|
||||
|
||||
if (null != $this->attendee) {
|
||||
$propertyBag->set('ATTENDEE', $this->attendee);
|
||||
}
|
||||
|
||||
return $propertyBag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Component;
|
||||
|
||||
use Eluceo\iCal\Component;
|
||||
use Eluceo\iCal\PropertyBag;
|
||||
|
||||
class Calendar extends Component
|
||||
{
|
||||
/**
|
||||
* Methods for calendar components.
|
||||
*
|
||||
* According to RFC 5545: 3.7.2. Method
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc5545#section-3.7.2
|
||||
*
|
||||
* And then according to RFC 2446: 3 APPLICATION PROTOCOL ELEMENTS
|
||||
* @see https://tools.ietf.org/html/rfc2446#section-3.2
|
||||
*/
|
||||
const METHOD_PUBLISH = 'PUBLISH';
|
||||
const METHOD_REQUEST = 'REQUEST';
|
||||
const METHOD_REPLY = 'REPLY';
|
||||
const METHOD_ADD = 'ADD';
|
||||
const METHOD_CANCEL = 'CANCEL';
|
||||
const METHOD_REFRESH = 'REFRESH';
|
||||
const METHOD_COUNTER = 'COUNTER';
|
||||
const METHOD_DECLINECOUNTER = 'DECLINECOUNTER';
|
||||
|
||||
/**
|
||||
* This property defines the calendar scale used for the calendar information specified in the iCalendar object.
|
||||
*
|
||||
* According to RFC 5545: 3.7.1. Calendar Scale
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc5545#section-3.7
|
||||
*/
|
||||
const CALSCALE_GREGORIAN = 'GREGORIAN';
|
||||
|
||||
/**
|
||||
* The Product Identifier.
|
||||
*
|
||||
* According to RFC 5545: 3.7.3 Product Identifier
|
||||
*
|
||||
* This property specifies the identifier for the product that created the Calendar object.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc5545#section-3.7.3
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $prodId = null;
|
||||
protected $method = null;
|
||||
protected $name = null;
|
||||
protected $description = null;
|
||||
protected $timezone = null;
|
||||
|
||||
/**
|
||||
* This property defines the calendar scale used for the
|
||||
* calendar information specified in the iCalendar object.
|
||||
*
|
||||
* Also identifies the calendar type of a non-Gregorian recurring appointment.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc5545#section-3.7
|
||||
* @see http://msdn.microsoft.com/en-us/library/ee237520(v=exchg.80).aspx
|
||||
*/
|
||||
protected $calendarScale = null;
|
||||
|
||||
/**
|
||||
* Specifies whether or not the iCalendar file only contains one appointment.
|
||||
*
|
||||
* @var bool
|
||||
*
|
||||
* @see http://msdn.microsoft.com/en-us/library/ee203486(v=exchg.80).aspx
|
||||
*/
|
||||
protected $forceInspectOrOpen = false;
|
||||
|
||||
/**
|
||||
* Specifies a globally unique identifier for the calendar.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @see http://msdn.microsoft.com/en-us/library/ee179588(v=exchg.80).aspx
|
||||
*/
|
||||
protected $calId = null;
|
||||
|
||||
/**
|
||||
* Specifies a suggested iCalendar file download frequency for clients and
|
||||
* servers with sync capabilities.
|
||||
*
|
||||
* For example you can set the value to 'P1W' if the calendar should be
|
||||
* synced once a week. Use 'P3H' to sync the file every 3 hours.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @see http://msdn.microsoft.com/en-us/library/ee178699(v=exchg.80).aspx
|
||||
*/
|
||||
protected $publishedTTL = null;
|
||||
|
||||
/**
|
||||
* Specifies a color for the calendar in calendar for Apple/Outlook.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @see http://msdn.microsoft.com/en-us/library/ee179588(v=exchg.80).aspx
|
||||
*/
|
||||
protected $calendarColor = null;
|
||||
|
||||
public function __construct($prodId)
|
||||
{
|
||||
if (empty($prodId)) {
|
||||
throw new \UnexpectedValueException('PRODID cannot be empty');
|
||||
}
|
||||
|
||||
$this->prodId = $prodId;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return 'VCALENDAR';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $method
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setMethod($method)
|
||||
{
|
||||
$this->method = $method;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $description
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $timezone
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTimezone($timezone)
|
||||
{
|
||||
$this->timezone = $timezone;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $calendarColor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCalendarColor($calendarColor)
|
||||
{
|
||||
$this->calendarColor = $calendarColor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $calendarScale
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCalendarScale($calendarScale)
|
||||
{
|
||||
$this->calendarScale = $calendarScale;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $forceInspectOrOpen
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setForceInspectOrOpen($forceInspectOrOpen)
|
||||
{
|
||||
$this->forceInspectOrOpen = $forceInspectOrOpen;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $calId
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCalId($calId)
|
||||
{
|
||||
$this->calId = $calId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $ttl
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPublishedTTL($ttl)
|
||||
{
|
||||
$this->publishedTTL = $ttl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildPropertyBag()
|
||||
{
|
||||
$propertyBag = new PropertyBag();
|
||||
$propertyBag->set('VERSION', '2.0');
|
||||
$propertyBag->set('PRODID', $this->prodId);
|
||||
|
||||
if ($this->method) {
|
||||
$propertyBag->set('METHOD', $this->method);
|
||||
}
|
||||
|
||||
if ($this->calendarColor) {
|
||||
$propertyBag->set('X-APPLE-CALENDAR-COLOR', $this->calendarColor);
|
||||
$propertyBag->set('X-OUTLOOK-COLOR', $this->calendarColor);
|
||||
$propertyBag->set('X-FUNAMBOL-COLOR', $this->calendarColor);
|
||||
}
|
||||
|
||||
if ($this->calendarScale) {
|
||||
$propertyBag->set('CALSCALE', $this->calendarScale);
|
||||
$propertyBag->set('X-MICROSOFT-CALSCALE', $this->calendarScale);
|
||||
}
|
||||
|
||||
if ($this->name) {
|
||||
$propertyBag->set('X-WR-CALNAME', $this->name);
|
||||
}
|
||||
|
||||
if ($this->description) {
|
||||
$propertyBag->set('X-WR-CALDESC', $this->description);
|
||||
}
|
||||
|
||||
if ($this->timezone) {
|
||||
if ($this->timezone instanceof Timezone) {
|
||||
$propertyBag->set('X-WR-TIMEZONE', $this->timezone->getZoneIdentifier());
|
||||
$this->addComponent($this->timezone);
|
||||
} else {
|
||||
$propertyBag->set('X-WR-TIMEZONE', $this->timezone);
|
||||
$this->addComponent(new Timezone($this->timezone));
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->forceInspectOrOpen) {
|
||||
$propertyBag->set('X-MS-OLK-FORCEINSPECTOROPEN', $this->forceInspectOrOpen);
|
||||
}
|
||||
|
||||
if ($this->calId) {
|
||||
$propertyBag->set('X-WR-RELCALID', $this->calId);
|
||||
}
|
||||
|
||||
if ($this->publishedTTL) {
|
||||
$propertyBag->set('X-PUBLISHED-TTL', $this->publishedTTL);
|
||||
}
|
||||
|
||||
return $propertyBag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Event to the Calendar.
|
||||
*
|
||||
* Wrapper for addComponent()
|
||||
*
|
||||
* @see Eluceo\iCal::addComponent
|
||||
* @deprecated Please, use public method addComponent() from abstract Component class
|
||||
*/
|
||||
public function addEvent(Event $event)
|
||||
{
|
||||
$this->addComponent($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProdId()
|
||||
{
|
||||
return $this->prodId;
|
||||
}
|
||||
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,941 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Component;
|
||||
|
||||
use Eluceo\iCal\Component;
|
||||
use Eluceo\iCal\Property;
|
||||
use Eluceo\iCal\Property\DateTimeProperty;
|
||||
use Eluceo\iCal\Property\DateTimesProperty;
|
||||
use Eluceo\iCal\Property\Event\Attachment;
|
||||
use Eluceo\iCal\Property\Event\Attendees;
|
||||
use Eluceo\iCal\Property\Event\Geo;
|
||||
use Eluceo\iCal\Property\Event\Organizer;
|
||||
use Eluceo\iCal\Property\Event\RecurrenceId;
|
||||
use Eluceo\iCal\Property\Event\RecurrenceRule;
|
||||
use Eluceo\iCal\Property\RawStringValue;
|
||||
use Eluceo\iCal\PropertyBag;
|
||||
|
||||
/**
|
||||
* Implementation of the EVENT component.
|
||||
*/
|
||||
class Event extends Component
|
||||
{
|
||||
const TIME_TRANSPARENCY_OPAQUE = 'OPAQUE';
|
||||
const TIME_TRANSPARENCY_TRANSPARENT = 'TRANSPARENT';
|
||||
|
||||
const STATUS_TENTATIVE = 'TENTATIVE';
|
||||
const STATUS_CONFIRMED = 'CONFIRMED';
|
||||
const STATUS_CANCELLED = 'CANCELLED';
|
||||
|
||||
const MS_BUSYSTATUS_FREE = 'FREE';
|
||||
const MS_BUSYSTATUS_TENTATIVE = 'TENTATIVE';
|
||||
const MS_BUSYSTATUS_BUSY = 'BUSY';
|
||||
const MS_BUSYSTATUS_OOF = 'OOF';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $uniqueId;
|
||||
|
||||
/**
|
||||
* The property indicates the date/time that the instance of
|
||||
* the iCalendar object was created.
|
||||
*
|
||||
* The value MUST be specified in the UTC time format.
|
||||
*
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $dtStamp;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $dtStart;
|
||||
|
||||
/**
|
||||
* Preferentially chosen over the duration if both are set.
|
||||
*
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $dtEnd;
|
||||
|
||||
/**
|
||||
* @var \DateInterval
|
||||
*/
|
||||
protected $duration;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $noTime = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $msBusyStatus = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $location;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $locationTitle;
|
||||
|
||||
/**
|
||||
* @var Geo
|
||||
*/
|
||||
protected $locationGeo;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $summary;
|
||||
|
||||
/**
|
||||
* @var Organizer
|
||||
*/
|
||||
protected $organizer;
|
||||
|
||||
/**
|
||||
* @see https://tools.ietf.org/html/rfc5545#section-3.8.2.7
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $transparency = self::TIME_TRANSPARENCY_OPAQUE;
|
||||
|
||||
/**
|
||||
* If set to true the timezone will be added to the event.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $useTimezone = false;
|
||||
|
||||
/**
|
||||
* If set will be used as the timezone identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $timezoneString = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $sequence = 0;
|
||||
|
||||
/**
|
||||
* @var Attendees
|
||||
*/
|
||||
protected $attendees;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $descriptionHTML;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* @var RecurrenceRule
|
||||
*/
|
||||
protected $recurrenceRule;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $recurrenceRules = [];
|
||||
|
||||
/**
|
||||
* This property specifies the date and time that the calendar
|
||||
* information was created.
|
||||
*
|
||||
* The value MUST be specified in the UTC time format.
|
||||
*
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $created;
|
||||
|
||||
/**
|
||||
* The property specifies the date and time that the information
|
||||
* associated with the calendar component was last revised.
|
||||
*
|
||||
* The value MUST be specified in the UTC time format.
|
||||
*
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $modified;
|
||||
|
||||
/**
|
||||
* Indicates if the UTC time should be used or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $useUtc = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $cancelled;
|
||||
|
||||
/**
|
||||
* This property is used to specify categories or subtypes
|
||||
* of the calendar component. The categories are useful in searching
|
||||
* for a calendar component of a particular type and category.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc5545#section-3.8.1.2
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $categories;
|
||||
|
||||
/**
|
||||
* https://tools.ietf.org/html/rfc5545#section-3.8.1.3.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isPrivate = false;
|
||||
|
||||
/**
|
||||
* Dates to be excluded from a series of events.
|
||||
*
|
||||
* @var \DateTimeInterface[]
|
||||
*/
|
||||
protected $exDates = [];
|
||||
|
||||
/**
|
||||
* @var RecurrenceId
|
||||
*/
|
||||
protected $recurrenceId;
|
||||
|
||||
/**
|
||||
* @var Attachment[]
|
||||
*/
|
||||
protected $attachments = [];
|
||||
|
||||
public function __construct(?string $uniqueId = null)
|
||||
{
|
||||
if (null == $uniqueId) {
|
||||
$uniqueId = uniqid();
|
||||
}
|
||||
|
||||
$this->uniqueId = $uniqueId;
|
||||
$this->attendees = new Attendees();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return 'VEVENT';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildPropertyBag()
|
||||
{
|
||||
$propertyBag = new PropertyBag();
|
||||
|
||||
// mandatory information
|
||||
$propertyBag->set('UID', $this->uniqueId);
|
||||
|
||||
$propertyBag->add(new DateTimeProperty('DTSTART', $this->dtStart, $this->noTime, $this->useTimezone, $this->useUtc, $this->timezoneString));
|
||||
$propertyBag->set('SEQUENCE', $this->sequence);
|
||||
$propertyBag->set('TRANSP', $this->transparency);
|
||||
|
||||
if ($this->status) {
|
||||
$propertyBag->set('STATUS', $this->status);
|
||||
}
|
||||
|
||||
// An event can have a 'dtend' or 'duration', but not both.
|
||||
if ($this->dtEnd !== null) {
|
||||
$dtEnd = clone $this->dtEnd;
|
||||
if ($this->noTime === true) {
|
||||
$dtEnd = $dtEnd->add(new \DateInterval('P1D'));
|
||||
}
|
||||
$propertyBag->add(new DateTimeProperty('DTEND', $dtEnd, $this->noTime, $this->useTimezone, $this->useUtc, $this->timezoneString));
|
||||
} elseif ($this->duration !== null) {
|
||||
$propertyBag->set('DURATION', $this->duration->format('P%dDT%hH%iM%sS'));
|
||||
}
|
||||
|
||||
// optional information
|
||||
if (null != $this->url) {
|
||||
$propertyBag->set('URL', $this->url);
|
||||
}
|
||||
|
||||
if (null != $this->location) {
|
||||
$propertyBag->set('LOCATION', $this->location);
|
||||
|
||||
if (null != $this->locationGeo) {
|
||||
$propertyBag->add(
|
||||
new Property(
|
||||
'X-APPLE-STRUCTURED-LOCATION',
|
||||
new RawStringValue('geo:' . $this->locationGeo->getGeoLocationAsString(',')),
|
||||
[
|
||||
'VALUE' => 'URI',
|
||||
'X-ADDRESS' => $this->location,
|
||||
'X-APPLE-RADIUS' => 49,
|
||||
'X-TITLE' => $this->locationTitle,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (null != $this->locationGeo) {
|
||||
$propertyBag->add($this->locationGeo);
|
||||
}
|
||||
|
||||
if (null != $this->summary) {
|
||||
$propertyBag->set('SUMMARY', $this->summary);
|
||||
}
|
||||
|
||||
if (null != $this->attendees) {
|
||||
$propertyBag->add($this->attendees);
|
||||
}
|
||||
|
||||
$propertyBag->set('CLASS', $this->isPrivate ? 'PRIVATE' : 'PUBLIC');
|
||||
|
||||
if (null != $this->description) {
|
||||
$propertyBag->set('DESCRIPTION', $this->description);
|
||||
}
|
||||
|
||||
if (null != $this->descriptionHTML) {
|
||||
$propertyBag->add(
|
||||
new Property(
|
||||
'X-ALT-DESC',
|
||||
$this->descriptionHTML,
|
||||
[
|
||||
'FMTTYPE' => 'text/html',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (null != $this->recurrenceRule) {
|
||||
$propertyBag->set('RRULE', $this->recurrenceRule);
|
||||
}
|
||||
|
||||
foreach ($this->recurrenceRules as $recurrenceRule) {
|
||||
$propertyBag->set('RRULE', $recurrenceRule);
|
||||
}
|
||||
|
||||
if (null != $this->recurrenceId) {
|
||||
$this->recurrenceId->applyTimeSettings($this->noTime, $this->useTimezone, $this->useUtc, $this->timezoneString);
|
||||
$propertyBag->add($this->recurrenceId);
|
||||
}
|
||||
|
||||
if (!empty($this->exDates)) {
|
||||
$propertyBag->add(new DateTimesProperty('EXDATE', $this->exDates, $this->noTime, $this->useTimezone, $this->useUtc, $this->timezoneString));
|
||||
}
|
||||
|
||||
if ($this->cancelled) {
|
||||
$propertyBag->set('STATUS', 'CANCELLED');
|
||||
}
|
||||
|
||||
if (null != $this->organizer) {
|
||||
$propertyBag->add($this->organizer);
|
||||
}
|
||||
|
||||
if ($this->noTime) {
|
||||
$propertyBag->set('X-MICROSOFT-CDO-ALLDAYEVENT', 'TRUE');
|
||||
}
|
||||
|
||||
if (null != $this->msBusyStatus) {
|
||||
$propertyBag->set('X-MICROSOFT-CDO-BUSYSTATUS', $this->msBusyStatus);
|
||||
$propertyBag->set('X-MICROSOFT-CDO-INTENDEDSTATUS', $this->msBusyStatus);
|
||||
}
|
||||
|
||||
if (null != $this->categories) {
|
||||
$propertyBag->set('CATEGORIES', $this->categories);
|
||||
}
|
||||
|
||||
$propertyBag->add(
|
||||
new DateTimeProperty('DTSTAMP', $this->dtStamp ?: new \DateTimeImmutable(), false, false, true)
|
||||
);
|
||||
|
||||
if ($this->created) {
|
||||
$propertyBag->add(new DateTimeProperty('CREATED', $this->created, false, false, true));
|
||||
}
|
||||
|
||||
if ($this->modified) {
|
||||
$propertyBag->add(new DateTimeProperty('LAST-MODIFIED', $this->modified, false, false, true));
|
||||
}
|
||||
|
||||
foreach ($this->attachments as $attachment) {
|
||||
$propertyBag->add($attachment);
|
||||
}
|
||||
|
||||
return $propertyBag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $dtEnd
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDtEnd($dtEnd)
|
||||
{
|
||||
$this->dtEnd = $dtEnd;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDtEnd()
|
||||
{
|
||||
return $this->dtEnd;
|
||||
}
|
||||
|
||||
public function setDtStart($dtStart)
|
||||
{
|
||||
$this->dtStart = $dtStart;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDtStart()
|
||||
{
|
||||
return $this->dtStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $dtStamp
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDtStamp($dtStamp)
|
||||
{
|
||||
$this->dtStamp = $dtStamp;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $duration
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDuration($duration)
|
||||
{
|
||||
$this->duration = $duration;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @param string $title
|
||||
* @param Geo|string $geo
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setLocation($location, $title = '', $geo = null)
|
||||
{
|
||||
if (is_scalar($geo)) {
|
||||
$geo = Geo::fromString($geo);
|
||||
} elseif (!is_null($geo) && !$geo instanceof Geo) {
|
||||
$className = get_class($geo);
|
||||
throw new \InvalidArgumentException("The parameter 'geo' must be a string or an instance of " . Geo::class . " but an instance of {$className} was given.");
|
||||
}
|
||||
|
||||
$this->location = $location;
|
||||
$this->locationTitle = $title;
|
||||
$this->locationGeo = $geo;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setGeoLocation(Geo $geoProperty)
|
||||
{
|
||||
$this->locationGeo = $geoProperty;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $noTime
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setNoTime($noTime)
|
||||
{
|
||||
$this->noTime = $noTime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $msBusyStatus
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setMsBusyStatus($msBusyStatus)
|
||||
{
|
||||
$msBusyStatus = strtoupper($msBusyStatus);
|
||||
if ($msBusyStatus == self::MS_BUSYSTATUS_FREE
|
||||
|| $msBusyStatus == self::MS_BUSYSTATUS_TENTATIVE
|
||||
|| $msBusyStatus == self::MS_BUSYSTATUS_BUSY
|
||||
|| $msBusyStatus == self::MS_BUSYSTATUS_OOF
|
||||
) {
|
||||
$this->msBusyStatus = $msBusyStatus;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid value for status');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getMsBusyStatus()
|
||||
{
|
||||
return $this->msBusyStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $sequence
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSequence($sequence)
|
||||
{
|
||||
$this->sequence = $sequence;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSequence()
|
||||
{
|
||||
return $this->sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setOrganizer(Organizer $organizer)
|
||||
{
|
||||
$this->organizer = $organizer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $summary
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSummary($summary)
|
||||
{
|
||||
$this->summary = $summary;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $uniqueId
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUniqueId($uniqueId)
|
||||
{
|
||||
$this->uniqueId = $uniqueId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueId()
|
||||
{
|
||||
return $this->uniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $url
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUrl($url)
|
||||
{
|
||||
$this->url = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $useTimezone
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUseTimezone($useTimezone)
|
||||
{
|
||||
$this->useTimezone = $useTimezone;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseTimezone()
|
||||
{
|
||||
return $this->useTimezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $timezoneString
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTimezoneString($timezoneString)
|
||||
{
|
||||
$this->timezoneString = $timezoneString;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getTimezoneString()
|
||||
{
|
||||
return $this->timezoneString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttendees(Attendees $attendees)
|
||||
{
|
||||
$this->attendees = $attendees;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attendee
|
||||
* @param array $params
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addAttendee($attendee, $params = [])
|
||||
{
|
||||
$this->attendees->add($attendee, $params);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAttendees(): Attendees
|
||||
{
|
||||
return $this->attendees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $description
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $descriptionHTML
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDescriptionHTML($descriptionHTML)
|
||||
{
|
||||
$this->descriptionHTML = $descriptionHTML;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $useUtc
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUseUtc($useUtc = true)
|
||||
{
|
||||
$this->useUtc = $useUtc;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescriptionHTML()
|
||||
{
|
||||
return $this->descriptionHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $status
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCancelled($status)
|
||||
{
|
||||
$this->cancelled = (bool) $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $transparency
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setTimeTransparency($transparency)
|
||||
{
|
||||
$transparency = strtoupper($transparency);
|
||||
if ($transparency === self::TIME_TRANSPARENCY_OPAQUE
|
||||
|| $transparency === self::TIME_TRANSPARENCY_TRANSPARENT
|
||||
) {
|
||||
$this->transparency = $transparency;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid value for transparancy');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $status
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setStatus($status)
|
||||
{
|
||||
$status = strtoupper($status);
|
||||
if ($status == self::STATUS_CANCELLED
|
||||
|| $status == self::STATUS_CONFIRMED
|
||||
|| $status == self::STATUS_TENTATIVE
|
||||
) {
|
||||
$this->status = $status;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid value for status');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated since version 0.11.0, to be removed in 1.0. Use addRecurrenceRule instead.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setRecurrenceRule(RecurrenceRule $recurrenceRule)
|
||||
{
|
||||
@trigger_error('setRecurrenceRule() is deprecated since version 0.11.0 and will be removed in 1.0. Use addRecurrenceRule instead.', E_USER_DEPRECATED);
|
||||
|
||||
$this->recurrenceRule = $recurrenceRule;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated since version 0.11.0, to be removed in 1.0. Use getRecurrenceRules instead.
|
||||
*
|
||||
* @return RecurrenceRule
|
||||
*/
|
||||
public function getRecurrenceRule()
|
||||
{
|
||||
@trigger_error('getRecurrenceRule() is deprecated since version 0.11.0 and will be removed in 1.0. Use getRecurrenceRules instead.', E_USER_DEPRECATED);
|
||||
|
||||
return $this->recurrenceRule;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addRecurrenceRule(RecurrenceRule $recurrenceRule)
|
||||
{
|
||||
$this->recurrenceRules[] = $recurrenceRule;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRecurrenceRules()
|
||||
{
|
||||
return $this->recurrenceRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $dtStamp
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCreated($dtStamp)
|
||||
{
|
||||
$this->created = $dtStamp;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $dtStamp
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setModified($dtStamp)
|
||||
{
|
||||
$this->modified = $dtStamp;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $categories
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCategories($categories)
|
||||
{
|
||||
$this->categories = $categories;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the event privacy.
|
||||
*
|
||||
* @param bool $flag
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setIsPrivate($flag)
|
||||
{
|
||||
$this->isPrivate = (bool) $flag;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Eluceo\iCal\Component\Event
|
||||
*/
|
||||
public function addExDate(\DateTimeInterface $dateTime)
|
||||
{
|
||||
$this->exDates[] = $dateTime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface[]
|
||||
*/
|
||||
public function getExDates()
|
||||
{
|
||||
return $this->exDates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface[]
|
||||
*
|
||||
* @return \Eluceo\iCal\Component\Event
|
||||
*/
|
||||
public function setExDates(array $exDates)
|
||||
{
|
||||
$this->exDates = $exDates;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Eluceo\iCal\Property\Event\RecurrenceId
|
||||
*/
|
||||
public function getRecurrenceId()
|
||||
{
|
||||
return $this->recurrenceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Eluceo\iCal\Component\Event
|
||||
*/
|
||||
public function setRecurrenceId(RecurrenceId $recurrenceId)
|
||||
{
|
||||
$this->recurrenceId = $recurrenceId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $attachment
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addAttachment(Attachment $attachment)
|
||||
{
|
||||
$this->attachments[] = $attachment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAttachments()
|
||||
{
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
public function addUrlAttachment(string $url)
|
||||
{
|
||||
$this->addAttachment(new Attachment($url));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Component;
|
||||
|
||||
use Eluceo\iCal\Component;
|
||||
use Eluceo\iCal\PropertyBag;
|
||||
|
||||
/**
|
||||
* Implementation of the TIMEZONE component.
|
||||
*/
|
||||
class Timezone extends Component
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $timezone;
|
||||
|
||||
public function __construct($timezone)
|
||||
{
|
||||
$this->timezone = $timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return 'VTIMEZONE';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildPropertyBag()
|
||||
{
|
||||
$propertyBag = new PropertyBag();
|
||||
|
||||
$propertyBag->set('TZID', $this->timezone);
|
||||
$propertyBag->set('X-LIC-LOCATION', $this->timezone);
|
||||
|
||||
return $propertyBag;
|
||||
}
|
||||
|
||||
public function getZoneIdentifier()
|
||||
{
|
||||
return $this->timezone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Component;
|
||||
|
||||
use Eluceo\iCal\Component;
|
||||
use Eluceo\iCal\Property\Event\RecurrenceRule;
|
||||
use Eluceo\iCal\PropertyBag;
|
||||
|
||||
/**
|
||||
* Implementation of Standard Time and Daylight Saving Time observances (or rules)
|
||||
* which define the TIMEZONE component.
|
||||
*/
|
||||
class TimezoneRule extends Component
|
||||
{
|
||||
const TYPE_DAYLIGHT = 'DAYLIGHT';
|
||||
const TYPE_STANDARD = 'STANDARD';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tzOffsetFrom;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tzOffsetTo;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tzName;
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
protected $dtStart;
|
||||
|
||||
/**
|
||||
* @var RecurrenceRule
|
||||
*/
|
||||
protected $recurrenceRule;
|
||||
|
||||
/**
|
||||
* create new Timezone Rule object by giving a rule type identifier.
|
||||
*
|
||||
* @param string $ruleType one of DAYLIGHT or STANDARD
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($ruleType)
|
||||
{
|
||||
$ruleType = strtoupper($ruleType);
|
||||
if ($ruleType === self::TYPE_DAYLIGHT || $ruleType === self::TYPE_STANDARD) {
|
||||
$this->type = $ruleType;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid value for timezone rule type');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildPropertyBag()
|
||||
{
|
||||
$propertyBag = new PropertyBag();
|
||||
|
||||
if ($this->getTzName()) {
|
||||
$propertyBag->set('TZNAME', $this->getTzName());
|
||||
}
|
||||
|
||||
if ($this->getTzOffsetFrom()) {
|
||||
$propertyBag->set('TZOFFSETFROM', $this->getTzOffsetFrom());
|
||||
}
|
||||
|
||||
if ($this->getTzOffsetTo()) {
|
||||
$propertyBag->set('TZOFFSETTO', $this->getTzOffsetTo());
|
||||
}
|
||||
|
||||
if ($this->getDtStart()) {
|
||||
$propertyBag->set('DTSTART', $this->getDtStart());
|
||||
}
|
||||
|
||||
if ($this->recurrenceRule) {
|
||||
$propertyBag->set('RRULE', $this->recurrenceRule);
|
||||
}
|
||||
|
||||
return $propertyBag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $offset
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTzOffsetFrom($offset)
|
||||
{
|
||||
$this->tzOffsetFrom = $offset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $offset
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTzOffsetTo($offset)
|
||||
{
|
||||
$this->tzOffsetTo = $offset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTzName($name)
|
||||
{
|
||||
$this->tzName = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setDtStart(\DateTimeInterface $dtStart)
|
||||
{
|
||||
$this->dtStart = $dtStart;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setRecurrenceRule(RecurrenceRule $recurrenceRule)
|
||||
{
|
||||
$this->recurrenceRule = $recurrenceRule;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTzOffsetFrom()
|
||||
{
|
||||
return $this->tzOffsetFrom;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTzOffsetTo()
|
||||
{
|
||||
return $this->tzOffsetTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTzName()
|
||||
{
|
||||
return $this->tzName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RecurrenceRule
|
||||
*/
|
||||
public function getRecurrenceRule()
|
||||
{
|
||||
return $this->recurrenceRule;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed return string representation of start date or NULL if no date was given
|
||||
*/
|
||||
public function getDtStart()
|
||||
{
|
||||
if ($this->dtStart) {
|
||||
return $this->dtStart->format('Ymd\THis');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012-2019 Markus Poerschke
|
||||
|
||||
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,107 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal;
|
||||
|
||||
class ParameterBag
|
||||
{
|
||||
/**
|
||||
* The params.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $params;
|
||||
|
||||
public function __construct($params = [])
|
||||
{
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setParam($name, $value)
|
||||
{
|
||||
$this->params[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
*
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function getParam($name)
|
||||
{
|
||||
if (isset($this->params[$name])) {
|
||||
return $this->params[$name];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any params.
|
||||
*/
|
||||
public function hasParams(): bool
|
||||
{
|
||||
return count($this->params) > 0;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
$line = '';
|
||||
foreach ($this->params as $param => $paramValues) {
|
||||
if (!is_array($paramValues)) {
|
||||
$paramValues = [$paramValues];
|
||||
}
|
||||
foreach ($paramValues as $k => $v) {
|
||||
$paramValues[$k] = $this->escapeParamValue($v);
|
||||
}
|
||||
|
||||
if ('' != $line) {
|
||||
$line .= ';';
|
||||
}
|
||||
|
||||
$line .= $param . '=' . implode(',', $paramValues);
|
||||
}
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an escaped string for a param value.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function escapeParamValue($value)
|
||||
{
|
||||
$count = 0;
|
||||
$value = str_replace('\\', '\\\\', $value);
|
||||
$value = str_replace('"', '\"', $value, $count);
|
||||
$value = str_replace("\n", '\\n', $value);
|
||||
if (false !== strpos($value, ';') || false !== strpos($value, ',') || false !== strpos($value, ':') || $count) {
|
||||
$value = '"' . $value . '"';
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal;
|
||||
|
||||
use Eluceo\iCal\Property\ArrayValue;
|
||||
use Eluceo\iCal\Property\StringValue;
|
||||
use Eluceo\iCal\Property\ValueInterface;
|
||||
|
||||
/**
|
||||
* The Property Class represents a property as defined in RFC 5545.
|
||||
*
|
||||
* The content of a line (unfolded) will be rendered in this class.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc5545#section-3.5
|
||||
*/
|
||||
class Property
|
||||
{
|
||||
/**
|
||||
* The value of the Property.
|
||||
*
|
||||
* @var ValueInterface
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* The params of the Property.
|
||||
*
|
||||
* @var ParameterBag
|
||||
*/
|
||||
protected $parameterBag;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $value
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct($name, $value, $params = [])
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->setValue($value);
|
||||
$this->parameterBag = new ParameterBag($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an unfolded line.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toLine()
|
||||
{
|
||||
// Property-name
|
||||
$line = $this->getName();
|
||||
|
||||
// Adding params
|
||||
//@todo added check for $this->parameterBag because doctrine/orm proxies won't execute constructor - ok?
|
||||
if ($this->parameterBag && $this->parameterBag->hasParams()) {
|
||||
$line .= ';' . $this->parameterBag->toString();
|
||||
}
|
||||
|
||||
// Property value
|
||||
$line .= ':' . $this->value->getEscapedValue();
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all unfolded lines.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toLines()
|
||||
{
|
||||
return [$this->toLine()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParam($name, $value)
|
||||
{
|
||||
$this->parameterBag->setParam($name, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
*/
|
||||
public function getParam($name)
|
||||
{
|
||||
return $this->parameterBag->getParam($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
if (is_scalar($value)) {
|
||||
$this->value = new StringValue($value);
|
||||
} elseif (is_array($value)) {
|
||||
$this->value = new ArrayValue($value);
|
||||
} else {
|
||||
if (!$value instanceof ValueInterface) {
|
||||
throw new \Exception('The value must implement the ValueInterface.');
|
||||
} else {
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property;
|
||||
|
||||
class ArrayValue implements ValueInterface
|
||||
{
|
||||
/**
|
||||
* The value.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $values;
|
||||
|
||||
public function __construct(array $values)
|
||||
{
|
||||
$this->values = $values;
|
||||
}
|
||||
|
||||
public function setValues(array $values)
|
||||
{
|
||||
$this->values = $values;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEscapedValue(): string
|
||||
{
|
||||
return implode(',', array_map(function (string $value): string {
|
||||
return (new StringValue($value))->getEscapedValue();
|
||||
}, $this->values));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property;
|
||||
|
||||
use Eluceo\iCal\Property;
|
||||
use Eluceo\iCal\Util\DateUtil;
|
||||
|
||||
class DateTimeProperty extends Property
|
||||
{
|
||||
/**
|
||||
* @param string $name
|
||||
* @param \DateTimeInterface $dateTime
|
||||
* @param bool $noTime
|
||||
* @param bool $useTimezone
|
||||
* @param bool $useUtc
|
||||
* @param string $timezoneString
|
||||
*/
|
||||
public function __construct(
|
||||
$name,
|
||||
?\DateTimeInterface $dateTime = null,
|
||||
$noTime = false,
|
||||
$useTimezone = false,
|
||||
$useUtc = false,
|
||||
$timezoneString = ''
|
||||
) {
|
||||
$dateString = DateUtil::getDateString($dateTime, $noTime, $useTimezone, $useUtc);
|
||||
$params = DateUtil::getDefaultParams($dateTime, $noTime, $useTimezone, $timezoneString);
|
||||
|
||||
parent::__construct($name, $dateString, $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property;
|
||||
|
||||
use Eluceo\iCal\Property;
|
||||
use Eluceo\iCal\Util\DateUtil;
|
||||
|
||||
class DateTimesProperty extends Property
|
||||
{
|
||||
/**
|
||||
* @param string $name
|
||||
* @param \DateTimeInterface[] $dateTimes
|
||||
* @param bool $noTime
|
||||
* @param bool $useTimezone
|
||||
* @param bool $useUtc
|
||||
* @param string $timezoneString
|
||||
*/
|
||||
public function __construct(
|
||||
$name,
|
||||
$dateTimes = [],
|
||||
$noTime = false,
|
||||
$useTimezone = false,
|
||||
$useUtc = false,
|
||||
$timezoneString = ''
|
||||
) {
|
||||
$dates = [];
|
||||
$dateTime = new \DateTimeImmutable();
|
||||
foreach ($dateTimes as $dateTime) {
|
||||
$dates[] = DateUtil::getDateString($dateTime, $noTime, $useTimezone, $useUtc);
|
||||
}
|
||||
|
||||
//@todo stop this triggering an E_NOTICE when $dateTimes is empty
|
||||
$params = DateUtil::getDefaultParams($dateTime, $noTime, $useTimezone, $timezoneString);
|
||||
|
||||
parent::__construct($name, $dates, $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property\Event;
|
||||
|
||||
use Eluceo\iCal\Property;
|
||||
|
||||
/**
|
||||
* Class Attachment.
|
||||
*/
|
||||
class Attachment extends Property
|
||||
{
|
||||
/**
|
||||
* @param string $value
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct($value, $params = [])
|
||||
{
|
||||
parent::__construct('ATTACH', $value, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $url
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setUrl($url)
|
||||
{
|
||||
$this->setValue($url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property\Event;
|
||||
|
||||
use Eluceo\iCal\Property;
|
||||
|
||||
class Attendees extends Property
|
||||
{
|
||||
/**
|
||||
* @var Property[]
|
||||
*/
|
||||
protected $attendees = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->name = 'ATTENDEES';
|
||||
// prevent super constructor to be called
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @param array $params
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function add($value, $params = [])
|
||||
{
|
||||
$this->attendees[] = new Property('ATTENDEE', $value, $params);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Property[] $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->attendees = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Property[]
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->attendees;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toLines()
|
||||
{
|
||||
$lines = [];
|
||||
foreach ($this->attendees as $attendee) {
|
||||
$lines[] = $attendee->toLine();
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function setParam($name, $value)
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot call setParam on Attendees Property');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function getParam($name)
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot call getParam on Attendees Property');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property\Event;
|
||||
|
||||
use Eluceo\iCal\Property;
|
||||
|
||||
/**
|
||||
* GEO property.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc5545#section-3.8.1.6
|
||||
*/
|
||||
class Geo extends Property
|
||||
{
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $latitude;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $longitude;
|
||||
|
||||
public function __construct(float $latitude, float $longitude)
|
||||
{
|
||||
$this->latitude = $latitude;
|
||||
$this->longitude = $longitude;
|
||||
|
||||
if ($this->latitude < -90 || $this->latitude > 90) {
|
||||
throw new \InvalidArgumentException("The geographical latitude must be a value between -90 and 90 degrees. '{$this->latitude}' was given.");
|
||||
}
|
||||
|
||||
if ($this->longitude < -180 || $this->longitude > 180) {
|
||||
throw new \InvalidArgumentException("The geographical longitude must be a value between -180 and 180 degrees. '{$this->longitude}' was given.");
|
||||
}
|
||||
|
||||
parent::__construct('GEO', new Property\RawStringValue($this->getGeoLocationAsString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method is used to allow backwards compatibility for Event::setLocation
|
||||
*
|
||||
* @return Geo
|
||||
*/
|
||||
public static function fromString(string $geoLocationString): self
|
||||
{
|
||||
$geoLocationString = str_replace(',', ';', $geoLocationString);
|
||||
$geoLocationString = str_replace('GEO:', '', $geoLocationString);
|
||||
$parts = explode(';', $geoLocationString);
|
||||
|
||||
return new static((float) $parts[0], (float) $parts[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the coordinates as a string.
|
||||
*
|
||||
* @example 37.386013;-122.082932
|
||||
*/
|
||||
public function getGeoLocationAsString(string $separator = ';'): string
|
||||
{
|
||||
return number_format($this->latitude, 6) . $separator . number_format($this->longitude, 6);
|
||||
}
|
||||
|
||||
public function getLatitude(): float
|
||||
{
|
||||
return $this->latitude;
|
||||
}
|
||||
|
||||
public function getLongitude(): float
|
||||
{
|
||||
return $this->longitude;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property\Event;
|
||||
|
||||
use Eluceo\iCal\Property;
|
||||
|
||||
/**
|
||||
* Class Organizer.
|
||||
*/
|
||||
class Organizer extends Property
|
||||
{
|
||||
/**
|
||||
* @param string $value
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct($value, $params = [])
|
||||
{
|
||||
parent::__construct('ORGANIZER', $value, $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property\Event;
|
||||
|
||||
use Eluceo\iCal\ParameterBag;
|
||||
use Eluceo\iCal\Property;
|
||||
use Eluceo\iCal\Property\ValueInterface;
|
||||
use Eluceo\iCal\Util\DateUtil;
|
||||
|
||||
/**
|
||||
* Implementation of Recurrence Id.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc5545#section-3.8.4.4
|
||||
*/
|
||||
class RecurrenceId extends Property
|
||||
{
|
||||
/**
|
||||
* The effective range of recurrence instances from the instance
|
||||
* specified by the recurrence identifier specified by the property.
|
||||
*/
|
||||
const RANGE_THISANDPRIOR = 'THISANDPRIOR';
|
||||
const RANGE_THISANDFUTURE = 'THISANDFUTURE';
|
||||
|
||||
/**
|
||||
* The dateTime to identify a particular instance of a recurring event which is getting modified.
|
||||
*
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
protected $dateTime;
|
||||
|
||||
/**
|
||||
* Specify the effective range of recurrence instances from the instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $range;
|
||||
|
||||
public function __construct(?\DateTimeInterface $dateTime = null)
|
||||
{
|
||||
$this->name = 'RECURRENCE-ID';
|
||||
$this->parameterBag = new ParameterBag();
|
||||
if (isset($dateTime)) {
|
||||
$this->dateTime = $dateTime;
|
||||
}
|
||||
}
|
||||
|
||||
public function applyTimeSettings($noTime = false, $useTimezone = false, $useUtc = false, $timezoneString = '')
|
||||
{
|
||||
$params = DateUtil::getDefaultParams($this->dateTime, $noTime, $useTimezone, $timezoneString);
|
||||
foreach ($params as $name => $value) {
|
||||
$this->parameterBag->setParam($name, $value);
|
||||
}
|
||||
|
||||
if ($this->range) {
|
||||
$this->parameterBag->setParam('RANGE', $this->range);
|
||||
}
|
||||
|
||||
$this->setValue(DateUtil::getDateString($this->dateTime, $noTime, $useTimezone, $useUtc));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getDatetime()
|
||||
{
|
||||
return $this->dateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Eluceo\iCal\Property\Event\RecurrenceId
|
||||
*/
|
||||
public function setDatetime(\DateTimeInterface $dateTime)
|
||||
{
|
||||
$this->dateTime = $dateTime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRange()
|
||||
{
|
||||
return $this->range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $range
|
||||
*
|
||||
* @return \Eluceo\iCal\Property\Event\RecurrenceId
|
||||
*/
|
||||
public function setRange($range)
|
||||
{
|
||||
$this->range = $range;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all unfolded lines.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toLines()
|
||||
{
|
||||
if (!$this->value instanceof ValueInterface) {
|
||||
throw new \Exception('The value must implement the ValueInterface. Call RecurrenceId::applyTimeSettings() before adding RecurrenceId.');
|
||||
} else {
|
||||
return parent::toLines();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property\Event;
|
||||
|
||||
use Eluceo\iCal\ParameterBag;
|
||||
use Eluceo\iCal\Property\ValueInterface;
|
||||
use InvalidArgumentException;
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* Implementation of Recurrence Rule.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc5545#section-3.8.5.3
|
||||
*/
|
||||
class RecurrenceRule implements ValueInterface
|
||||
{
|
||||
const FREQ_YEARLY = 'YEARLY';
|
||||
const FREQ_MONTHLY = 'MONTHLY';
|
||||
const FREQ_WEEKLY = 'WEEKLY';
|
||||
const FREQ_DAILY = 'DAILY';
|
||||
const FREQ_HOURLY = 'HOURLY';
|
||||
const FREQ_MINUTELY = 'MINUTELY';
|
||||
const FREQ_SECONDLY = 'SECONDLY';
|
||||
|
||||
const WEEKDAY_SUNDAY = 'SU';
|
||||
const WEEKDAY_MONDAY = 'MO';
|
||||
const WEEKDAY_TUESDAY = 'TU';
|
||||
const WEEKDAY_WEDNESDAY = 'WE';
|
||||
const WEEKDAY_THURSDAY = 'TH';
|
||||
const WEEKDAY_FRIDAY = 'FR';
|
||||
const WEEKDAY_SATURDAY = 'SA';
|
||||
|
||||
/**
|
||||
* The frequency of an Event.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $freq = self::FREQ_YEARLY;
|
||||
|
||||
/**
|
||||
* BYSETPOS must require use of other BY*.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $canUseBySetPos = false;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
protected $interval = 1;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
protected $count = null;
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface|null
|
||||
*/
|
||||
protected $until = null;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $wkst;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
protected $bySetPos = null;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $byMonth;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $byWeekNo;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $byYearDay;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $byMonthDay;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $byDay;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $byHour;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $byMinute;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $bySecond;
|
||||
|
||||
public function getEscapedValue(): string
|
||||
{
|
||||
return $this->buildParameterBag()->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ParameterBag
|
||||
*/
|
||||
protected function buildParameterBag()
|
||||
{
|
||||
$parameterBag = new ParameterBag();
|
||||
|
||||
$parameterBag->setParam('FREQ', $this->freq);
|
||||
|
||||
if (null !== $this->interval) {
|
||||
$parameterBag->setParam('INTERVAL', $this->interval);
|
||||
}
|
||||
|
||||
if (null !== $this->count) {
|
||||
$parameterBag->setParam('COUNT', $this->count);
|
||||
}
|
||||
|
||||
if (null != $this->until) {
|
||||
$parameterBag->setParam('UNTIL', $this->until->format('Ymd\THis\Z'));
|
||||
}
|
||||
|
||||
if (null !== $this->wkst) {
|
||||
$parameterBag->setParam('WKST', $this->wkst);
|
||||
}
|
||||
|
||||
if (null !== $this->bySetPos && $this->canUseBySetPos) {
|
||||
$parameterBag->setParam('BYSETPOS', $this->bySetPos);
|
||||
}
|
||||
|
||||
if (null !== $this->byMonth) {
|
||||
$parameterBag->setParam('BYMONTH', explode(',', $this->byMonth));
|
||||
}
|
||||
|
||||
if (null !== $this->byWeekNo) {
|
||||
$parameterBag->setParam('BYWEEKNO', explode(',', $this->byWeekNo));
|
||||
}
|
||||
|
||||
if (null !== $this->byYearDay) {
|
||||
$parameterBag->setParam('BYYEARDAY', explode(',', $this->byYearDay));
|
||||
}
|
||||
|
||||
if (null !== $this->byMonthDay) {
|
||||
$parameterBag->setParam('BYMONTHDAY', explode(',', $this->byMonthDay));
|
||||
}
|
||||
|
||||
if (null !== $this->byDay) {
|
||||
$parameterBag->setParam('BYDAY', explode(',', $this->byDay));
|
||||
}
|
||||
|
||||
if (null !== $this->byHour) {
|
||||
$parameterBag->setParam('BYHOUR', explode(',', $this->byHour));
|
||||
}
|
||||
|
||||
if (null !== $this->byMinute) {
|
||||
$parameterBag->setParam('BYMINUTE', explode(',', $this->byMinute));
|
||||
}
|
||||
|
||||
if (null !== $this->bySecond) {
|
||||
$parameterBag->setParam('BYSECOND', explode(',', $this->bySecond));
|
||||
}
|
||||
|
||||
return $parameterBag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $count
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCount($count)
|
||||
{
|
||||
$this->count = $count;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getCount()
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setUntil(?DateTimeInterface $until = null)
|
||||
{
|
||||
$this->until = $until;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface|null
|
||||
*/
|
||||
public function getUntil()
|
||||
{
|
||||
return $this->until;
|
||||
}
|
||||
|
||||
/**
|
||||
* The FREQ rule part identifies the type of recurrence rule. This
|
||||
* rule part MUST be specified in the recurrence rule. Valid values
|
||||
* include.
|
||||
*
|
||||
* SECONDLY, to specify repeating events based on an interval of a second or more;
|
||||
* MINUTELY, to specify repeating events based on an interval of a minute or more;
|
||||
* HOURLY, to specify repeating events based on an interval of an hour or more;
|
||||
* DAILY, to specify repeating events based on an interval of a day or more;
|
||||
* WEEKLY, to specify repeating events based on an interval of a week or more;
|
||||
* MONTHLY, to specify repeating events based on an interval of a month or more;
|
||||
* YEARLY, to specify repeating events based on an interval of a year or more.
|
||||
*
|
||||
* @param string $freq
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setFreq($freq)
|
||||
{
|
||||
if (@constant('static::FREQ_' . $freq) !== null) {
|
||||
$this->freq = $freq;
|
||||
} else {
|
||||
throw new \InvalidArgumentException("The Frequency {$freq} is not supported.");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFreq()
|
||||
{
|
||||
return $this->freq;
|
||||
}
|
||||
|
||||
/**
|
||||
* The INTERVAL rule part contains a positive integer representing at
|
||||
* which intervals the recurrence rule repeats.
|
||||
*
|
||||
* @param int|null $interval
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setInterval($interval)
|
||||
{
|
||||
$this->interval = $interval;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getInterval()
|
||||
{
|
||||
return $this->interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* The WKST rule part specifies the day on which the workweek starts.
|
||||
* Valid values are MO, TU, WE, TH, FR, SA, and SU.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setWkst($value)
|
||||
{
|
||||
$this->wkst = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BYSETPOS filters one interval of events by the specified position.
|
||||
* A positive position will start from the beginning and go forward while
|
||||
* a negative position will start at the end and move backward.
|
||||
*
|
||||
* Valid values are a comma separated string or an array of integers
|
||||
* from 1 to 366 or negative integers from -1 to -366.
|
||||
*
|
||||
* @param int|string|array|null $value
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBySetPos($value)
|
||||
{
|
||||
if (null === $value) {
|
||||
$this->bySetPos = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!(is_string($value) || is_array($value) || is_int($value))) {
|
||||
throw new InvalidArgumentException('Invalid value for BYSETPOS');
|
||||
}
|
||||
|
||||
$list = $value;
|
||||
|
||||
if (is_int($value)) {
|
||||
if ($value === 0 || $value < -366 || $value > 366) {
|
||||
throw new InvalidArgumentException('Invalid value for BYSETPOS');
|
||||
}
|
||||
$this->bySetPos = [$value];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$list = explode(',', $value);
|
||||
}
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($list as $item) {
|
||||
if (is_string($item)) {
|
||||
if (!preg_match('/^ *-?[0-9]* *$/', $item)) {
|
||||
throw new InvalidArgumentException('Invalid value for BYSETPOS');
|
||||
}
|
||||
$item = intval($item);
|
||||
}
|
||||
|
||||
if (!is_int($item) || $item === 0 || $item < -366 || $item > 366) {
|
||||
throw new InvalidArgumentException('Invalid value for BYSETPOS');
|
||||
}
|
||||
|
||||
$output[] = $item;
|
||||
}
|
||||
|
||||
$this->bySetPos = $output;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BYMONTH rule part specifies a COMMA-separated list of months of the year.
|
||||
* Valid values are 1 to 12.
|
||||
*
|
||||
* @param int $month
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setByMonth($month)
|
||||
{
|
||||
if (!is_integer($month) || $month <= 0 || $month > 12) {
|
||||
throw new InvalidArgumentException('Invalid value for BYMONTH');
|
||||
}
|
||||
|
||||
$this->byMonth = $month;
|
||||
|
||||
$this->canUseBySetPos = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BYWEEKNO rule part specifies a COMMA-separated list of ordinals specifying weeks of the year.
|
||||
* Valid values are 1 to 53 or -53 to -1.
|
||||
*
|
||||
* @param int $value
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setByWeekNo($value)
|
||||
{
|
||||
if (!is_integer($value) || $value > 53 || $value < -53 || $value === 0) {
|
||||
throw new InvalidArgumentException('Invalid value for BYWEEKNO');
|
||||
}
|
||||
|
||||
$this->byWeekNo = $value;
|
||||
|
||||
$this->canUseBySetPos = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BYYEARDAY rule part specifies a COMMA-separated list of days of the year.
|
||||
* Valid values are 1 to 366 or -366 to -1.
|
||||
*
|
||||
* @param int $day
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setByYearDay($day)
|
||||
{
|
||||
if (!is_integer($day) || $day > 366 || $day < -366 || $day === 0) {
|
||||
throw new InvalidArgumentException('Invalid value for BYYEARDAY');
|
||||
}
|
||||
|
||||
$this->byYearDay = $day;
|
||||
|
||||
$this->canUseBySetPos = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BYMONTHDAY rule part specifies a COMMA-separated list of days of the month.
|
||||
* Valid values are 1 to 31 or -31 to -1.
|
||||
*
|
||||
* @param int $day
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setByMonthDay($day)
|
||||
{
|
||||
if (!is_integer($day) || $day > 31 || $day < -31 || $day === 0) {
|
||||
throw new InvalidArgumentException('Invalid value for BYMONTHDAY');
|
||||
}
|
||||
|
||||
$this->byMonthDay = $day;
|
||||
|
||||
$this->canUseBySetPos = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BYDAY rule part specifies a COMMA-separated list of days of the week;.
|
||||
*
|
||||
* SU indicates Sunday; MO indicates Monday; TU indicates Tuesday;
|
||||
* WE indicates Wednesday; TH indicates Thursday; FR indicates Friday; and SA indicates Saturday.
|
||||
*
|
||||
* Each BYDAY value can also be preceded by a positive (+n) or negative (-n) integer.
|
||||
* If present, this indicates the nth occurrence of a specific day within the MONTHLY or YEARLY "RRULE".
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setByDay(string $day)
|
||||
{
|
||||
$this->byDay = $day;
|
||||
|
||||
$this->canUseBySetPos = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BYHOUR rule part specifies a COMMA-separated list of hours of the day.
|
||||
* Valid values are 0 to 23.
|
||||
*
|
||||
* @param int $value
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setByHour($value)
|
||||
{
|
||||
if (!is_integer($value) || $value < 0 || $value > 23) {
|
||||
throw new \InvalidArgumentException('Invalid value for BYHOUR');
|
||||
}
|
||||
|
||||
$this->byHour = $value;
|
||||
|
||||
$this->canUseBySetPos = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BYMINUTE rule part specifies a COMMA-separated list of minutes within an hour.
|
||||
* Valid values are 0 to 59.
|
||||
*
|
||||
* @param int $value
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setByMinute($value)
|
||||
{
|
||||
if (!is_integer($value) || $value < 0 || $value > 59) {
|
||||
throw new \InvalidArgumentException('Invalid value for BYMINUTE');
|
||||
}
|
||||
|
||||
$this->byMinute = $value;
|
||||
|
||||
$this->canUseBySetPos = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BYSECOND rule part specifies a COMMA-separated list of seconds within a minute.
|
||||
* Valid values are 0 to 60.
|
||||
*
|
||||
* @param int $value
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setBySecond($value)
|
||||
{
|
||||
if (!is_integer($value) || $value < 0 || $value > 60) {
|
||||
throw new \InvalidArgumentException('Invalid value for BYSECOND');
|
||||
}
|
||||
|
||||
$this->bySecond = $value;
|
||||
|
||||
$this->canUseBySetPos = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property;
|
||||
|
||||
class RawStringValue extends StringValue
|
||||
{
|
||||
public function getEscapedValue(): string
|
||||
{
|
||||
return $this->getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property;
|
||||
|
||||
class StringValue implements ValueInterface
|
||||
{
|
||||
/**
|
||||
* The value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getEscapedValue(): string
|
||||
{
|
||||
$value = $this->value;
|
||||
|
||||
$value = str_replace('\\', '\\\\', $value);
|
||||
$value = str_replace('"', '\\"', $value);
|
||||
$value = str_replace(',', '\\,', $value);
|
||||
$value = str_replace(';', '\\;', $value);
|
||||
$value = str_replace("\n", '\\n', $value);
|
||||
$value = str_replace([
|
||||
"\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07",
|
||||
"\x08", "\x09", /* \n*/ "\x0B", "\x0C", "\x0D", "\x0E", "\x0F",
|
||||
"\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17",
|
||||
"\x18", "\x19", "\x1A", "\x1B", "\x1C", "\x1D", "\x1E", "\x1F",
|
||||
"\x7F",
|
||||
], '', $value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Property;
|
||||
|
||||
interface ValueInterface
|
||||
{
|
||||
/**
|
||||
* Return the value of the Property as an escaped string.
|
||||
*
|
||||
* Escape values as per RFC 5545.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc5545#section-3.3.11
|
||||
*/
|
||||
public function getEscapedValue(): string;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal;
|
||||
|
||||
class PropertyBag implements \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $elements = [];
|
||||
|
||||
/**
|
||||
* Creates a new Property with $name, $value and $params.
|
||||
*
|
||||
* @param $name
|
||||
* @param $value
|
||||
* @param array $params
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function set($name, $value, $params = [])
|
||||
{
|
||||
$this->add(new Property($name, $value, $params));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Property|null
|
||||
*/
|
||||
public function get(string $name)
|
||||
{
|
||||
if (isset($this->elements[$name])) {
|
||||
return $this->elements[$name];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Property. If Property already exists an Exception will be thrown.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function add(Property $property)
|
||||
{
|
||||
$name = $property->getName();
|
||||
|
||||
if (isset($this->elements[$name])) {
|
||||
throw new \Exception("Property with name '{$name}' already exists");
|
||||
}
|
||||
|
||||
$this->elements[$name] = $property;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayObject($this->elements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Util;
|
||||
|
||||
class ComponentUtil
|
||||
{
|
||||
/**
|
||||
* Folds a single line.
|
||||
*
|
||||
* According to RFC 5545, all lines longer than 75 characters should be folded
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc5545#section-5
|
||||
* @see https://tools.ietf.org/html/rfc5545#section-3.1
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function fold($string)
|
||||
{
|
||||
$lines = [];
|
||||
|
||||
if (function_exists('mb_strcut')) {
|
||||
while (strlen($string) > 0) {
|
||||
if (strlen($string) > 75) {
|
||||
$lines[] = mb_strcut($string, 0, 75, 'utf-8');
|
||||
$string = ' ' . mb_strcut($string, 75, strlen($string), 'utf-8');
|
||||
} else {
|
||||
$lines[] = $string;
|
||||
$string = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$array = preg_split('/(?<!^)(?!$)/u', $string);
|
||||
$line = '';
|
||||
$lineNo = 0;
|
||||
foreach ($array as $char) {
|
||||
$charLen = strlen($char);
|
||||
$lineLen = strlen($line);
|
||||
if ($lineLen + $charLen > 75) {
|
||||
$line = ' ' . $char;
|
||||
++$lineNo;
|
||||
} else {
|
||||
$line .= $char;
|
||||
}
|
||||
$lines[$lineNo] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the eluceo/iCal package.
|
||||
*
|
||||
* (c) Markus Poerschke <markus@eluceo.de>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace Eluceo\iCal\Util;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
class DateUtil
|
||||
{
|
||||
public static function getDefaultParams(?DateTimeInterface $dateTime = null, $noTime = false, $useTimezone = false, $timezoneString = '')
|
||||
{
|
||||
$params = [];
|
||||
|
||||
if ($useTimezone && $noTime === false) {
|
||||
$timeZone = $timezoneString === '' ? $dateTime->getTimezone()->getName() : $timezoneString;
|
||||
$params['TZID'] = $timeZone;
|
||||
}
|
||||
|
||||
if ($noTime) {
|
||||
$params['VALUE'] = 'DATE';
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted date string.
|
||||
*
|
||||
* @param \DateTimeInterface|null $dateTime The DateTime object
|
||||
* @param bool $noTime Indicates if the time will be added
|
||||
* @param bool $useTimezone
|
||||
* @param bool $useUtc
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getDateString(?DateTimeInterface $dateTime = null, $noTime = false, $useTimezone = false, $useUtc = false)
|
||||
{
|
||||
if (empty($dateTime)) {
|
||||
$dateTime = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
// Only convert the DateTime to UTC if there is a time present. For date-only the
|
||||
// timezone is meaningless and converting it might shift it to the wrong date.
|
||||
// Do not convert DateTime to UTC if a timezone it specified, as it should be local time.
|
||||
if (!$noTime && $useUtc && !$useTimezone) {
|
||||
$dateTime = clone $dateTime;
|
||||
$dateTime = $dateTime->setTimezone(new \DateTimeZone('UTC'));
|
||||
}
|
||||
|
||||
return $dateTime->format(self::getDateFormat($noTime, $useTimezone, $useUtc));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date format that can be passed to DateTime::format().
|
||||
*
|
||||
* @param bool $noTime Indicates if the time will be added
|
||||
* @param bool $useTimezone
|
||||
* @param bool $useUtc
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDateFormat($noTime = false, $useTimezone = false, $useUtc = false)
|
||||
{
|
||||
// Do not use UTC time (Z) if timezone support is enabled.
|
||||
if ($useTimezone || !$useUtc) {
|
||||
return $noTime ? 'Ymd' : 'Ymd\THis';
|
||||
}
|
||||
|
||||
return $noTime ? 'Ymd' : 'Ymd\THis\Z';
|
||||
}
|
||||
}
|
||||
@@ -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,412 @@
|
||||
JsonRPC PHP Client and Server
|
||||
=============================
|
||||
|
||||
A simple Json-RPC client/server that just works.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- JSON-RPC 2.0 only
|
||||
- The server support batch requests and notifications
|
||||
- Authentication and IP based client restrictions
|
||||
- Custom Middleware
|
||||
- Fully unit tested
|
||||
- Requirements: PHP >= 5.3.4
|
||||
- License: MIT
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
Frédéric Guillot
|
||||
|
||||
Installation with Composer
|
||||
--------------------------
|
||||
|
||||
```bash
|
||||
composer require fguillot/json-rpc @stable
|
||||
```
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
### Server
|
||||
|
||||
Callback binding:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Server;
|
||||
|
||||
$server = new Server();
|
||||
$server->getProcedureHandler()
|
||||
->withCallback('addition', function ($a, $b) {
|
||||
return $a + $b;
|
||||
})
|
||||
->withCallback('random', function ($start, $end) {
|
||||
return mt_rand($start, $end);
|
||||
})
|
||||
;
|
||||
|
||||
echo $server->execute();
|
||||
```
|
||||
|
||||
Callback binding from array:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Server;
|
||||
|
||||
$callbacks = array(
|
||||
'getA' => function() { return 'A'; },
|
||||
'getB' => function() { return 'B'; },
|
||||
'getC' => function() { return 'C'; }
|
||||
);
|
||||
|
||||
$server = new Server();
|
||||
$server->getProcedureHandler()->withCallbackArray($callbacks);
|
||||
|
||||
echo $server->execute();
|
||||
```
|
||||
|
||||
Class/Method binding:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Server;
|
||||
|
||||
class Api
|
||||
{
|
||||
public function doSomething($arg1, $arg2 = 3)
|
||||
{
|
||||
return $arg1 + $arg2;
|
||||
}
|
||||
}
|
||||
|
||||
$server = new Server();
|
||||
$procedureHandler = $server->getProcedureHandler();
|
||||
|
||||
// Bind the method Api::doSomething() to the procedure myProcedure
|
||||
$procedureHandler->withClassAndMethod('myProcedure', 'Api', 'doSomething');
|
||||
|
||||
// Use a class instance instead of the class name
|
||||
$procedureHandler->withClassAndMethod('mySecondProcedure', new Api, 'doSomething');
|
||||
|
||||
// The procedure and the method are the same
|
||||
$procedureHandler->withClassAndMethod('doSomething', 'Api');
|
||||
|
||||
// Attach the class, the client will be able to call directly Api::doSomething()
|
||||
$procedureHandler->withObject(new Api());
|
||||
|
||||
echo $server->execute();
|
||||
```
|
||||
|
||||
Class/Method binding from array:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Server;
|
||||
|
||||
class MathApi
|
||||
{
|
||||
public function addition($arg1, $arg2)
|
||||
{
|
||||
return $arg1 + $arg2;
|
||||
}
|
||||
|
||||
public function subtraction($arg1, $arg2)
|
||||
{
|
||||
return $arg1 - $arg2;
|
||||
}
|
||||
|
||||
public function multiplication($arg1, $arg2)
|
||||
{
|
||||
return $arg1 * $arg2;
|
||||
}
|
||||
|
||||
public function division($arg1, $arg2)
|
||||
{
|
||||
return $arg1 / $arg2;
|
||||
}
|
||||
}
|
||||
|
||||
$callbacks = array(
|
||||
'addition' => array( 'MathApi', addition ),
|
||||
'subtraction' => array( 'MathApi', subtraction ),
|
||||
'multiplication' => array( 'MathApi', multiplication ),
|
||||
'division' => array( 'MathApi', division )
|
||||
);
|
||||
|
||||
$server = new Server();
|
||||
$server->getProcedureHandler()->withClassAndMethodArray($callbacks);
|
||||
|
||||
echo $server->execute();
|
||||
```
|
||||
|
||||
Server Middleware:
|
||||
|
||||
Middleware might be used to authenticate and authorize the client.
|
||||
They are executed before each procedure.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Server;
|
||||
use JsonRPC\MiddlewareInterface;
|
||||
use JsonRPC\Exception\AuthenticationFailureException;
|
||||
|
||||
class Api
|
||||
{
|
||||
public function doSomething($arg1, $arg2 = 3)
|
||||
{
|
||||
return $arg1 + $arg2;
|
||||
}
|
||||
}
|
||||
|
||||
class MyMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function execute($username, $password, $procedureName)
|
||||
{
|
||||
if ($username !== 'foobar') {
|
||||
throw new AuthenticationFailureException('Wrong credentials!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$server = new Server();
|
||||
$server->getMiddlewareHandler()->withMiddleware(new MyMiddleware());
|
||||
$server->getProcedureHandler()->withObject(new Api());
|
||||
echo $server->execute();
|
||||
```
|
||||
|
||||
You can raise a `AuthenticationFailureException` when the API credentials are wrong or a `AccessDeniedException` when the user is not allowed to access to the procedure.
|
||||
|
||||
### Client
|
||||
|
||||
Example with positional parameters:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Client;
|
||||
|
||||
$client = new Client('http://localhost/server.php');
|
||||
$result = $client->execute('addition', [3, 5]);
|
||||
```
|
||||
|
||||
Example with named arguments:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Client;
|
||||
|
||||
$client = new Client('http://localhost/server.php');
|
||||
$result = $client->execute('random', ['end' => 10, 'start' => 1]);
|
||||
```
|
||||
|
||||
Arguments are called in the right order.
|
||||
|
||||
Examples with the magic method `__call()`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Client;
|
||||
|
||||
$client = new Client('http://localhost/server.php');
|
||||
$result = $client->random(50, 100);
|
||||
```
|
||||
|
||||
The example above use positional arguments for the request and this one use named arguments:
|
||||
|
||||
```php
|
||||
$result = $client->random(['end' => 10, 'start' => 1]);
|
||||
```
|
||||
|
||||
### Client batch requests
|
||||
|
||||
Call several procedures in a single HTTP request:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Client;
|
||||
|
||||
$client = new Client('http://localhost/server.php');
|
||||
|
||||
$results = $client->batch()
|
||||
->foo(['arg1' => 'bar'])
|
||||
->random(1, 100)
|
||||
->add(4, 3)
|
||||
->execute('add', [2, 5])
|
||||
->send();
|
||||
|
||||
print_r($results);
|
||||
```
|
||||
|
||||
All results are stored at the same position of the call.
|
||||
|
||||
### Client exceptions
|
||||
|
||||
Client exceptions are normally thrown when an error is returned by the server. You can change this behaviour by
|
||||
using the `$returnException` argument which causes exceptions to be returned. This can be extremely useful when
|
||||
executing the batch request.
|
||||
|
||||
- `BadFunctionCallException`: Procedure not found on the server
|
||||
- `InvalidArgumentException`: Wrong procedure arguments
|
||||
- `JsonRPC\Exception\AccessDeniedException`: Access denied
|
||||
- `JsonRPC\Exception\ConnectionFailureException`: Connection failure
|
||||
- `JsonRPC\Exception\ServerErrorException`: Internal server error
|
||||
|
||||
### Enable client debugging
|
||||
|
||||
You can enable the debug mode to see the JSON request and response:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Client;
|
||||
|
||||
$client = new Client('http://localhost/server.php');
|
||||
$client->getHttpClient()->withDebug();
|
||||
```
|
||||
|
||||
The debug output is sent to the PHP system logger.
|
||||
You can configure the log destination in your `php.ini`.
|
||||
|
||||
Output example:
|
||||
|
||||
```json
|
||||
==> Request:
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "removeCategory",
|
||||
"id": 486782327,
|
||||
"params": [
|
||||
1
|
||||
]
|
||||
}
|
||||
==> Response:
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 486782327,
|
||||
"result": true
|
||||
}
|
||||
```
|
||||
|
||||
### IP based client restrictions
|
||||
|
||||
The server can allow only some IP addresses:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Server;
|
||||
|
||||
$server = new Server;
|
||||
|
||||
// IP client restrictions
|
||||
$server->allowHosts(['192.168.0.1', '127.0.0.1']);
|
||||
|
||||
[...]
|
||||
|
||||
// Return the response to the client
|
||||
echo $server->execute();
|
||||
```
|
||||
|
||||
If the client is blocked, you got a 403 Forbidden HTTP response.
|
||||
|
||||
### HTTP Basic Authentication
|
||||
|
||||
If you use HTTPS, you can allow client by using a username/password.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Server;
|
||||
|
||||
$server = new Server;
|
||||
|
||||
// List of users to allow
|
||||
$server->authentication(['user1' => 'password1', 'user2' => 'password2']);
|
||||
|
||||
[...]
|
||||
|
||||
// Return the response to the client
|
||||
echo $server->execute();
|
||||
```
|
||||
|
||||
On the client, set credentials like that:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Client;
|
||||
|
||||
$client = new Client('http://localhost/server.php');
|
||||
$client->getHttpClient()
|
||||
->withUsername('Foo')
|
||||
->withPassword('Bar');
|
||||
```
|
||||
|
||||
If the authentication failed, the client throw a RuntimeException.
|
||||
|
||||
Using an alternative authentication header:
|
||||
|
||||
```php
|
||||
|
||||
use JsonRPC\Server;
|
||||
|
||||
$server = new Server();
|
||||
$server->setAuthenticationHeader('X-Authentication');
|
||||
$server->authentication(['myusername' => 'mypassword']);
|
||||
```
|
||||
|
||||
The example above will use the HTTP header `X-Authentication` instead of the standard `Authorization: Basic [BASE64_CREDENTIALS]`.
|
||||
The username/password values need be encoded in base64: `base64_encode('username:password')`.
|
||||
|
||||
### Local Exceptions
|
||||
|
||||
By default, the server will relay all exceptions to the client.
|
||||
If you would like to relay only some of them, use the method `Server::withLocalException($exception)`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use JsonRPC\Server;
|
||||
class MyException1 extends Exception {};
|
||||
class MyException2 extends Exception {};
|
||||
|
||||
$server = new Server();
|
||||
|
||||
// Exceptions that should NOT be relayed to the client, if they occurs
|
||||
$server
|
||||
->withLocalException('MyException1')
|
||||
->withLocalException('MyException2')
|
||||
;
|
||||
|
||||
[...]
|
||||
|
||||
echo $server->execute();
|
||||
```
|
||||
|
||||
### Callback before client request
|
||||
|
||||
You can use a callback to change the HTTP headers or the URL before to make the request to the server.
|
||||
|
||||
Example:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$client = new Client();
|
||||
$client->getHttpClient()->withBeforeRequestCallback(function(HttpClient $client, $payload) {
|
||||
$client->withHeaders(array('Content-Length: '.strlen($payload)));
|
||||
});
|
||||
|
||||
$client->myProcedure(123);
|
||||
```
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC;
|
||||
|
||||
use Exception;
|
||||
use JsonRPC\Request\RequestBuilder;
|
||||
use JsonRPC\Response\ResponseParser;
|
||||
|
||||
/**
|
||||
* JsonRPC client class
|
||||
*
|
||||
* @package JsonRPC
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
/**
|
||||
* If the only argument passed to a function is an array
|
||||
* assume it contains named arguments
|
||||
*
|
||||
* @access private
|
||||
* @var boolean
|
||||
*/
|
||||
private $isNamedArguments = true;
|
||||
|
||||
/**
|
||||
* Do not immediately throw an exception on error. Return it instead.
|
||||
*
|
||||
* @access public
|
||||
* @var boolean
|
||||
*/
|
||||
private $returnException = false;
|
||||
|
||||
/**
|
||||
* True for a batch request
|
||||
*
|
||||
* @access private
|
||||
* @var boolean
|
||||
*/
|
||||
private $isBatch = false;
|
||||
|
||||
/**
|
||||
* Batch payload
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $batch = array();
|
||||
|
||||
/**
|
||||
* Http Client
|
||||
*
|
||||
* @access private
|
||||
* @var HttpClient
|
||||
*/
|
||||
private $httpClient;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param string $url Server URL
|
||||
* @param bool $returnException Return exceptions
|
||||
* @param HttpClient $httpClient HTTP client object
|
||||
*/
|
||||
public function __construct($url = '', $returnException = false, ?HttpClient $httpClient = null)
|
||||
{
|
||||
$this->httpClient = $httpClient ?: new HttpClient($url);
|
||||
$this->returnException = $returnException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arguments passed are always positional
|
||||
*
|
||||
* @access public
|
||||
* @return $this
|
||||
*/
|
||||
public function withPositionalArguments()
|
||||
{
|
||||
$this->isNamedArguments = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTTP Client
|
||||
*
|
||||
* @access public
|
||||
* @return HttpClient
|
||||
*/
|
||||
public function getHttpClient()
|
||||
{
|
||||
return $this->httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set username and password
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return $this
|
||||
*/
|
||||
public function authentication($username, $password)
|
||||
{
|
||||
$this->httpClient
|
||||
->withUsername($username)
|
||||
->withPassword($password);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatic mapping of procedures
|
||||
*
|
||||
* @access public
|
||||
* @param string $method Procedure name
|
||||
* @param array $params Procedure arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, array $params)
|
||||
{
|
||||
if ($this->isNamedArguments && count($params) === 1 && is_array($params[0])) {
|
||||
$params = $params[0];
|
||||
}
|
||||
|
||||
return $this->execute($method, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a batch request
|
||||
*
|
||||
* @access public
|
||||
* @return Client
|
||||
*/
|
||||
public function batch()
|
||||
{
|
||||
$this->isBatch = true;
|
||||
$this->batch = array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a batch request
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function send()
|
||||
{
|
||||
$this->isBatch = false;
|
||||
return $this->sendPayload('['.implode(', ', $this->batch).']');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a procedure
|
||||
*
|
||||
* @access public
|
||||
* @param string $procedure Procedure name
|
||||
* @param array $params Procedure arguments
|
||||
* @param array $reqattrs
|
||||
* @param string|null $requestId Request Id
|
||||
* @param string[] $headers Headers for this request
|
||||
* @return mixed
|
||||
*/
|
||||
public function execute($procedure, array $params = array(), array $reqattrs = array(), $requestId = null, array $headers = array())
|
||||
{
|
||||
$payload = RequestBuilder::create()
|
||||
->withProcedure($procedure)
|
||||
->withParams($params)
|
||||
->withRequestAttributes($reqattrs)
|
||||
->withId($requestId)
|
||||
->build();
|
||||
|
||||
if ($this->isBatch) {
|
||||
$this->batch[] = $payload;
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->sendPayload($payload, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send payload
|
||||
*
|
||||
* @access private
|
||||
* @throws Exception
|
||||
* @param string $payload
|
||||
* @param string[] $headers
|
||||
* @return Exception|Client
|
||||
*/
|
||||
private function sendPayload($payload, array $headers = array())
|
||||
{
|
||||
return ResponseParser::create()
|
||||
->withReturnException($this->returnException)
|
||||
->withPayload($this->httpClient->execute($payload, $headers))
|
||||
->parse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC\Exception;
|
||||
|
||||
/**
|
||||
* Class AccessDeniedException
|
||||
*
|
||||
* @package JsonRPC\Exception
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AccessDeniedException extends RpcCallFailedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC\Exception;
|
||||
|
||||
/**
|
||||
* Class AuthenticationFailureException
|
||||
*
|
||||
* @package JsonRPC\Exception
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AuthenticationFailureException extends RpcCallFailedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC\Exception;
|
||||
|
||||
/**
|
||||
* Class ConnectionFailureException
|
||||
*
|
||||
* @package JsonRPC\Exception
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ConnectionFailureException extends RpcCallFailedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC\Exception;
|
||||
|
||||
/**
|
||||
* Class InvalidJsonFormatException
|
||||
*
|
||||
* @package JsonRPC\Exception
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class InvalidJsonFormatException extends RpcCallFailedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC\Exception;
|
||||
|
||||
/**
|
||||
* Class InvalidJsonRpcFormatException
|
||||
*
|
||||
* @package JsonRPC\Exception
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class InvalidJsonRpcFormatException extends RpcCallFailedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC\Exception;
|
||||
|
||||
/**
|
||||
* Class ResponseEncodingFailureException
|
||||
*
|
||||
* @package JsonRPC\Exception
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ResponseEncodingFailureException extends RpcCallFailedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class ResponseException
|
||||
*
|
||||
* @package JsonRPC\Exception
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ResponseException extends RpcCallFailedException
|
||||
{
|
||||
/**
|
||||
* A value that contains additional information about the error.
|
||||
*
|
||||
* @access protected
|
||||
* @link http://www.jsonrpc.org/specification#error_object
|
||||
* @var mixed
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param string $message [optional] The Exception message to throw.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param Exception $previous [optional] The previous exception used for the exception chaining. Since 5.3.0
|
||||
* @param mixed $data [optional] A value that contains additional information about the error.
|
||||
*/
|
||||
public function __construct($message = '', $code = 0, ?Exception $previous = null, $data = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->setData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach additional information
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $data [optional] A value that contains additional information about the error.
|
||||
* @return \JsonRPC\Exception\ResponseException
|
||||
*/
|
||||
public function setData($data = null)
|
||||
{
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additional information
|
||||
*
|
||||
* @access public
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class RpcCallFailedException
|
||||
*
|
||||
* @package JsonRPC\Exception
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class RpcCallFailedException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC\Exception;
|
||||
|
||||
/**
|
||||
* Class ServerErrorException
|
||||
*
|
||||
* @package JsonRPC\Exception
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ServerErrorException extends RpcCallFailedException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC;
|
||||
|
||||
use Closure;
|
||||
use JsonRPC\Exception\AccessDeniedException;
|
||||
use JsonRPC\Exception\ConnectionFailureException;
|
||||
use JsonRPC\Exception\ServerErrorException;
|
||||
|
||||
/**
|
||||
* Class HttpClient
|
||||
*
|
||||
* @package JsonRPC
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class HttpClient
|
||||
{
|
||||
/**
|
||||
* URL of the server
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* HTTP client timeout
|
||||
*
|
||||
* @access protected
|
||||
* @var integer
|
||||
*/
|
||||
protected $timeout = 5;
|
||||
|
||||
/**
|
||||
* Default HTTP headers to send to the server
|
||||
*
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
protected $headers = array(
|
||||
'User-Agent: JSON-RPC PHP Client <https://github.com/fguillot/JsonRPC>',
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
'Connection: close',
|
||||
);
|
||||
|
||||
/**
|
||||
* Username for authentication
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $username;
|
||||
|
||||
/**
|
||||
* Password for authentication
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $password;
|
||||
|
||||
/**
|
||||
* Enable debug output to the php error log
|
||||
*
|
||||
* @access protected
|
||||
* @var boolean
|
||||
*/
|
||||
protected $debug = false;
|
||||
|
||||
/**
|
||||
* Cookies
|
||||
*
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
protected $cookies = array();
|
||||
|
||||
/**
|
||||
* SSL certificates verification
|
||||
*
|
||||
* @access protected
|
||||
* @var boolean
|
||||
*/
|
||||
protected $verifySslCertificate = true;
|
||||
|
||||
/**
|
||||
* SSL client certificate
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $sslLocalCert;
|
||||
|
||||
/**
|
||||
* Callback called before the doing the request
|
||||
*
|
||||
* @access protected
|
||||
* @var Closure
|
||||
*/
|
||||
protected $beforeRequest;
|
||||
|
||||
/**
|
||||
* HttpClient constructor
|
||||
*
|
||||
* @access public
|
||||
* @param string $url
|
||||
*/
|
||||
public function __construct($url = '')
|
||||
{
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set URL
|
||||
*
|
||||
* @access public
|
||||
* @param string $url
|
||||
* @return $this
|
||||
*/
|
||||
public function withUrl($url)
|
||||
{
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set username
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @return $this
|
||||
*/
|
||||
public function withUsername($username)
|
||||
{
|
||||
$this->username = $username;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password
|
||||
*
|
||||
* @access public
|
||||
* @param string $password
|
||||
* @return $this
|
||||
*/
|
||||
public function withPassword($password)
|
||||
{
|
||||
$this->password = $password;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set timeout
|
||||
*
|
||||
* @access public
|
||||
* @param integer $timeout
|
||||
* @return $this
|
||||
*/
|
||||
public function withTimeout($timeout)
|
||||
{
|
||||
$this->timeout = $timeout;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set headers
|
||||
*
|
||||
* @access public
|
||||
* @param array $headers
|
||||
* @return $this
|
||||
*/
|
||||
public function withHeaders(array $headers)
|
||||
{
|
||||
$this->headers = array_merge($this->headers, $headers);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cookies
|
||||
*
|
||||
* @access public
|
||||
* @param array $cookies
|
||||
* @param boolean $replace
|
||||
*/
|
||||
public function withCookies(array $cookies, $replace = false)
|
||||
{
|
||||
if ($replace) {
|
||||
$this->cookies = $cookies;
|
||||
} else {
|
||||
$this->cookies = array_merge($this->cookies, $cookies);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable debug mode
|
||||
*
|
||||
* @access public
|
||||
* @return $this
|
||||
*/
|
||||
public function withDebug()
|
||||
{
|
||||
$this->debug = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable SSL verification
|
||||
*
|
||||
* @access public
|
||||
* @return $this
|
||||
*/
|
||||
public function withoutSslVerification()
|
||||
{
|
||||
$this->verifySslCertificate = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a certificate to use TLS
|
||||
*
|
||||
* @access public
|
||||
* @return $this
|
||||
*/
|
||||
public function withSslLocalCert($path)
|
||||
{
|
||||
$this->sslLocalCert = $path;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a callback before the request
|
||||
*
|
||||
* @access public
|
||||
* @param Closure $closure
|
||||
* @return $this
|
||||
*/
|
||||
public function withBeforeRequestCallback(Closure $closure)
|
||||
{
|
||||
$this->beforeRequest = $closure;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cookies
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getCookies()
|
||||
{
|
||||
return $this->cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the HTTP request
|
||||
*
|
||||
* @access public
|
||||
* @throws ConnectionFailureException
|
||||
* @param string $payload
|
||||
* @param string[] $headers Headers for this request
|
||||
* @return array
|
||||
*/
|
||||
public function execute($payload, array $headers = array())
|
||||
{
|
||||
if (is_callable($this->beforeRequest)) {
|
||||
call_user_func_array($this->beforeRequest, array($this, $payload, $headers));
|
||||
}
|
||||
|
||||
if ($this->isCurlLoaded()) {
|
||||
$ch = curl_init();
|
||||
$requestHeaders = $this->buildHeaders($headers);
|
||||
$headers = array();
|
||||
curl_setopt_array($ch, array(
|
||||
CURLOPT_URL => trim($this->url),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CONNECTTIMEOUT => $this->timeout,
|
||||
CURLOPT_MAXREDIRS => 2,
|
||||
CURLOPT_SSL_VERIFYPEER => $this->verifySslCertificate,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HTTPHEADER => $requestHeaders,
|
||||
CURLOPT_HEADERFUNCTION => function ($curl, $header) use (&$headers) {
|
||||
$headers[] = $header;
|
||||
return strlen($header);
|
||||
}
|
||||
));
|
||||
if ($this->sslLocalCert !== null) {
|
||||
curl_setopt($ch, CURLOPT_CAINFO, $this->sslLocalCert);
|
||||
}
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
if ($response !== false) {
|
||||
$response = json_decode($response, true);
|
||||
} else {
|
||||
throw new ConnectionFailureException('Unable to establish a connection');
|
||||
}
|
||||
} else {
|
||||
$stream = fopen(trim($this->url), 'r', false, $this->buildContext($payload, $headers));
|
||||
|
||||
if (! is_resource($stream)) {
|
||||
throw new ConnectionFailureException('Unable to establish a connection');
|
||||
}
|
||||
|
||||
$metadata = stream_get_meta_data($stream);
|
||||
$headers = $metadata['wrapper_data'];
|
||||
$response = json_decode(stream_get_contents($stream), true);
|
||||
|
||||
fclose($stream);
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
error_log('==> Request: '.PHP_EOL.(is_string($payload) ? $payload : json_encode($payload, JSON_PRETTY_PRINT)));
|
||||
error_log('==> Headers: '.PHP_EOL.var_export($headers, true));
|
||||
error_log('==> Response: '.PHP_EOL.json_encode($response, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
$this->handleExceptions($headers);
|
||||
$this->parseCookies($headers);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare stream context
|
||||
*
|
||||
* @access protected
|
||||
* @param string $payload
|
||||
* @param string[] $headers
|
||||
* @return resource
|
||||
*/
|
||||
protected function buildContext($payload, array $headers = array())
|
||||
{
|
||||
$headers = $this->buildHeaders($headers);
|
||||
|
||||
$options = array(
|
||||
'http' => array(
|
||||
'method' => 'POST',
|
||||
'protocol_version' => 1.1,
|
||||
'timeout' => $this->timeout,
|
||||
'max_redirects' => 2,
|
||||
'header' => implode("\r\n", $headers),
|
||||
'content' => $payload,
|
||||
'ignore_errors' => true,
|
||||
),
|
||||
'ssl' => array(
|
||||
'verify_peer' => $this->verifySslCertificate,
|
||||
'verify_peer_name' => $this->verifySslCertificate,
|
||||
)
|
||||
);
|
||||
|
||||
if ($this->sslLocalCert !== null) {
|
||||
$options['ssl']['local_cert'] = $this->sslLocalCert;
|
||||
}
|
||||
|
||||
return stream_context_create($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse cookies from response
|
||||
*
|
||||
* @access protected
|
||||
* @param array $headers
|
||||
*/
|
||||
protected function parseCookies(array $headers)
|
||||
{
|
||||
foreach ($headers as $header) {
|
||||
$pos = stripos($header, 'Set-Cookie:');
|
||||
|
||||
if ($pos !== false) {
|
||||
$cookies = explode(';', substr($header, $pos + 11));
|
||||
|
||||
foreach ($cookies as $cookie) {
|
||||
$item = explode('=', $cookie);
|
||||
|
||||
if (count($item) === 2) {
|
||||
$name = trim($item[0]);
|
||||
$value = $item[1];
|
||||
$this->cookies[$name] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception according the HTTP response
|
||||
*
|
||||
* @access public
|
||||
* @param array $headers
|
||||
* @throws AccessDeniedException
|
||||
* @throws ServerErrorException
|
||||
*/
|
||||
public function handleExceptions(array $headers)
|
||||
{
|
||||
$exceptions = array(
|
||||
'401' => '\JsonRPC\Exception\AccessDeniedException',
|
||||
'403' => '\JsonRPC\Exception\AccessDeniedException',
|
||||
'404' => '\JsonRPC\Exception\ConnectionFailureException',
|
||||
'500' => '\JsonRPC\Exception\ServerErrorException',
|
||||
);
|
||||
|
||||
foreach ($headers as $header) {
|
||||
foreach ($exceptions as $code => $exception) {
|
||||
if (strpos($header, 'HTTP/1.0 '.$code) !== false || strpos($header, 'HTTP/1.1 '.$code) !== false) {
|
||||
throw new $exception('Response: '.$header);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the curl extension is loaded
|
||||
*
|
||||
* @access protected
|
||||
* @return bool
|
||||
*/
|
||||
protected function isCurlLoaded()
|
||||
{
|
||||
return extension_loaded('curl');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare Headers
|
||||
*
|
||||
* @access protected
|
||||
* @param array $headers
|
||||
* @return array
|
||||
*/
|
||||
protected function buildHeaders(array $headers)
|
||||
{
|
||||
$headers = array_merge($this->headers, $headers);
|
||||
|
||||
if (!empty($this->username) && !empty($this->password)) {
|
||||
$headers[] = 'Authorization: Basic ' . base64_encode($this->username . ':' . $this->password);
|
||||
}
|
||||
|
||||
if (!empty($this->cookies)) {
|
||||
$cookies = array();
|
||||
|
||||
foreach ($this->cookies as $key => $value) {
|
||||
$cookies[] = $key . '=' . $value;
|
||||
}
|
||||
|
||||
$headers[] = 'Cookie: ' . implode('; ', $cookies);
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC;
|
||||
|
||||
/**
|
||||
* Class MiddlewareHandler
|
||||
*
|
||||
* @package JsonRPC
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class MiddlewareHandler
|
||||
{
|
||||
/**
|
||||
* Procedure Name
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $procedureName = '';
|
||||
|
||||
/**
|
||||
* Username
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $username = '';
|
||||
|
||||
/**
|
||||
* Password
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $password = '';
|
||||
|
||||
/**
|
||||
* List of middleware to execute before to call the method
|
||||
*
|
||||
* @access protected
|
||||
* @var MiddlewareInterface[]
|
||||
*/
|
||||
protected $middleware = array();
|
||||
|
||||
/**
|
||||
* Set username
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @return $this
|
||||
*/
|
||||
public function withUsername($username)
|
||||
{
|
||||
if (! empty($username)) {
|
||||
$this->username = $username;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password
|
||||
*
|
||||
* @access public
|
||||
* @param string $password
|
||||
* @return $this
|
||||
*/
|
||||
public function withPassword($password)
|
||||
{
|
||||
if (! empty($password)) {
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set procedure name
|
||||
*
|
||||
* @access public
|
||||
* @param string $procedureName
|
||||
* @return $this
|
||||
*/
|
||||
public function withProcedure($procedureName)
|
||||
{
|
||||
$this->procedureName = $procedureName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new middleware
|
||||
*
|
||||
* @access public
|
||||
* @param MiddlewareInterface $middleware
|
||||
* @return MiddlewareHandler
|
||||
*/
|
||||
public function withMiddleware(MiddlewareInterface $middleware)
|
||||
{
|
||||
$this->middleware[] = $middleware;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all middleware
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
foreach ($this->middleware as $middleware) {
|
||||
$middleware->execute($this->username, $this->password, $this->procedureName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC;
|
||||
|
||||
use JsonRPC\Exception\AccessDeniedException;
|
||||
use JsonRPC\Exception\AuthenticationFailureException;
|
||||
|
||||
/**
|
||||
* Interface MiddlewareInterface
|
||||
*
|
||||
* @package JsonRPC
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Execute Middleware
|
||||
*
|
||||
* @access public
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $procedureName
|
||||
* @throws AccessDeniedException
|
||||
* @throws AuthenticationFailureException
|
||||
*/
|
||||
public function execute($username, $password, $procedureName);
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC;
|
||||
|
||||
use BadFunctionCallException;
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* Class ProcedureHandler
|
||||
*
|
||||
* @package JsonRPC
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProcedureHandler
|
||||
{
|
||||
/**
|
||||
* List of procedures
|
||||
*
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
protected $callbacks = array();
|
||||
|
||||
/**
|
||||
* List of classes
|
||||
*
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
protected $classes = array();
|
||||
|
||||
/**
|
||||
* List of instances
|
||||
*
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
protected $instances = array();
|
||||
|
||||
/**
|
||||
* Before method name to call
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $beforeMethodName = '';
|
||||
|
||||
/**
|
||||
* Register a new procedure
|
||||
*
|
||||
* @access public
|
||||
* @param string $procedure Procedure name
|
||||
* @param closure $callback Callback
|
||||
* @return $this
|
||||
*/
|
||||
public function withCallback($procedure, Closure $callback)
|
||||
{
|
||||
$this->callbacks[$procedure] = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a procedure to a class
|
||||
*
|
||||
* @access public
|
||||
* @param string $procedure Procedure name
|
||||
* @param mixed $class Class name or instance
|
||||
* @param string $method Procedure name
|
||||
* @return $this
|
||||
*/
|
||||
public function withClassAndMethod($procedure, $class, $method = '')
|
||||
{
|
||||
if ($method === '') {
|
||||
$method = $procedure;
|
||||
}
|
||||
|
||||
$this->classes[$procedure] = array($class, $method);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a class instance
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $instance
|
||||
* @return $this
|
||||
*/
|
||||
public function withObject($instance)
|
||||
{
|
||||
$this->instances[] = $instance;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a before method to call
|
||||
*
|
||||
* @access public
|
||||
* @param string $methodName
|
||||
* @return $this
|
||||
*/
|
||||
public function withBeforeMethod($methodName)
|
||||
{
|
||||
$this->beforeMethodName = $methodName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register multiple procedures from array
|
||||
*
|
||||
* @access public
|
||||
* @param array $callbacks Array with procedure names (array keys) and callbacks (array values)
|
||||
* @return $this
|
||||
*/
|
||||
public function withCallbackArray($callbacks)
|
||||
{
|
||||
foreach ($callbacks as $procedure => $callback) {
|
||||
$this->withCallback($procedure, $callback);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind multiple procedures to classes from array
|
||||
*
|
||||
* @access public
|
||||
* @param array $callbacks Array with procedure names (array keys) and class and method names (array values)
|
||||
* @return $this
|
||||
*/
|
||||
public function withClassAndMethodArray($callbacks)
|
||||
{
|
||||
foreach ($callbacks as $procedure => $callback) {
|
||||
$this->withClassAndMethod($procedure, $callback[0], $callback[1]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the procedure
|
||||
*
|
||||
* @access public
|
||||
* @param string $procedure Procedure name
|
||||
* @param array $params Procedure params
|
||||
* @return mixed
|
||||
*/
|
||||
public function executeProcedure($procedure, array $params = array())
|
||||
{
|
||||
if (isset($this->callbacks[$procedure])) {
|
||||
return $this->executeCallback($this->callbacks[$procedure], $params);
|
||||
} elseif (isset($this->classes[$procedure]) && method_exists($this->classes[$procedure][0], $this->classes[$procedure][1])) {
|
||||
return $this->executeMethod($this->classes[$procedure][0], $this->classes[$procedure][1], $params);
|
||||
}
|
||||
|
||||
foreach ($this->instances as $instance) {
|
||||
if (method_exists($instance, $procedure)) {
|
||||
return $this->executeMethod($instance, $procedure, $params);
|
||||
}
|
||||
}
|
||||
|
||||
throw new BadFunctionCallException('Unable to find the procedure');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback
|
||||
*
|
||||
* @access public
|
||||
* @param Closure $callback Callback
|
||||
* @param array $params Procedure params
|
||||
* @return mixed
|
||||
*/
|
||||
public function executeCallback(Closure $callback, $params)
|
||||
{
|
||||
$reflection = new ReflectionFunction($callback);
|
||||
|
||||
$arguments = $this->getArguments(
|
||||
$params,
|
||||
$reflection->getParameters(),
|
||||
$reflection->getNumberOfRequiredParameters(),
|
||||
$reflection->getNumberOfParameters()
|
||||
);
|
||||
|
||||
return $reflection->invokeArgs($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a method
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $class Class name or instance
|
||||
* @param string $method Method name
|
||||
* @param array $params Procedure params
|
||||
* @return mixed
|
||||
*/
|
||||
public function executeMethod($class, $method, $params)
|
||||
{
|
||||
$instance = is_string($class) ? new $class : $class;
|
||||
$reflection = new ReflectionMethod($class, $method);
|
||||
|
||||
$this->executeBeforeMethod($instance, $method);
|
||||
|
||||
$arguments = $this->getArguments(
|
||||
$params,
|
||||
$reflection->getParameters(),
|
||||
$reflection->getNumberOfRequiredParameters(),
|
||||
$reflection->getNumberOfParameters()
|
||||
);
|
||||
|
||||
return $reflection->invokeArgs($instance, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute before method if defined
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $object
|
||||
* @param string $method
|
||||
*/
|
||||
public function executeBeforeMethod($object, $method)
|
||||
{
|
||||
if ($this->beforeMethodName !== '' && method_exists($object, $this->beforeMethodName)) {
|
||||
call_user_func_array(array($object, $this->beforeMethodName), array($method));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get procedure arguments
|
||||
*
|
||||
* @access public
|
||||
* @param array $requestParams Incoming arguments
|
||||
* @param array $methodParams Procedure arguments
|
||||
* @param integer $nbRequiredParams Number of required parameters
|
||||
* @param integer $nbMaxParams Maximum number of parameters
|
||||
* @return array
|
||||
*/
|
||||
public function getArguments(array $requestParams, array $methodParams, $nbRequiredParams, $nbMaxParams)
|
||||
{
|
||||
$nbParams = count($requestParams);
|
||||
|
||||
if ($nbParams < $nbRequiredParams) {
|
||||
throw new InvalidArgumentException('Wrong number of arguments');
|
||||
}
|
||||
|
||||
if ($nbParams > $nbMaxParams) {
|
||||
throw new InvalidArgumentException('Too many arguments');
|
||||
}
|
||||
|
||||
if ($this->isPositionalArguments($requestParams)) {
|
||||
return $requestParams;
|
||||
}
|
||||
|
||||
return $this->getNamedArguments($requestParams, $methodParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we have positional parameters
|
||||
*
|
||||
* @access public
|
||||
* @param array $request_params Incoming arguments
|
||||
* @return bool
|
||||
*/
|
||||
public function isPositionalArguments(array $request_params)
|
||||
{
|
||||
return array_keys($request_params) === range(0, count($request_params) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get named arguments
|
||||
*
|
||||
* @access public
|
||||
* @param array $requestParams Incoming arguments
|
||||
* @param array $methodParams Procedure arguments
|
||||
* @return array
|
||||
*/
|
||||
public function getNamedArguments(array $requestParams, array $methodParams)
|
||||
{
|
||||
$params = array();
|
||||
|
||||
foreach ($methodParams as $p) {
|
||||
$name = $p->getName();
|
||||
|
||||
if (array_key_exists($name, $requestParams)) {
|
||||
$params[$name] = $requestParams[$name];
|
||||
} elseif ($p->isDefaultValueAvailable()) {
|
||||
$params[$name] = $p->getDefaultValue();
|
||||
} else {
|
||||
throw new InvalidArgumentException('Missing argument: '.$name);
|
||||
}
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC\Request;
|
||||
|
||||
/**
|
||||
* Class BatchRequestParser
|
||||
*
|
||||
* @package JsonRPC\Request
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class BatchRequestParser extends RequestParser
|
||||
{
|
||||
/**
|
||||
* Parse incoming request
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function parse()
|
||||
{
|
||||
$responses = array();
|
||||
|
||||
foreach ($this->payload as $payload) {
|
||||
$responses[] = RequestParser::create()
|
||||
->withPayload($payload)
|
||||
->withProcedureHandler($this->procedureHandler)
|
||||
->withMiddlewareHandler($this->middlewareHandler)
|
||||
->withLocalException($this->localExceptions)
|
||||
->parse();
|
||||
}
|
||||
|
||||
$responses = array_filter($responses);
|
||||
return empty($responses) ? '' : '['.implode(',', $responses).']';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we have a batch request
|
||||
*
|
||||
* ex : [
|
||||
* 0 => '...',
|
||||
* 1 => '...',
|
||||
* 2 => '...',
|
||||
* 3 => '...',
|
||||
* ]
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param array $payload
|
||||
* @return bool
|
||||
*/
|
||||
public static function isBatchRequest(array $payload)
|
||||
{
|
||||
return array_keys($payload) === range(0, count($payload) - 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace JsonRPC\Request;
|
||||
|
||||
/**
|
||||
* Class RequestBuilder
|
||||
*
|
||||
* @package JsonRPC\Request
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class RequestBuilder
|
||||
{
|
||||
/**
|
||||
* Request ID
|
||||
*
|
||||
* @access private
|
||||
* @var mixed
|
||||
*/
|
||||
private $id = null;
|
||||
|
||||
/**
|
||||
* Method name
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $procedure = '';
|
||||
|
||||
/**
|
||||
* Method arguments
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $params = array();
|
||||
|
||||
/**
|
||||
* Additional request attributes
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $reqattrs = array();
|
||||
|
||||
/**
|
||||
* Get new object instance
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return RequestBuilder
|
||||
*/
|
||||
public static function create()
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set id
|
||||
*
|
||||
* @access public
|
||||
* @param null $id
|
||||
* @return RequestBuilder
|
||||
*/
|
||||
public function withId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set method
|
||||
*
|
||||
* @access public
|
||||
* @param string $procedure
|
||||
* @return RequestBuilder
|
||||
*/
|
||||
public function withProcedure($procedure)
|
||||
{
|
||||
$this->procedure = $procedure;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters
|
||||
*
|
||||
* @access public
|
||||
* @param array $params
|
||||
* @return RequestBuilder
|
||||
*/
|
||||
public function withParams(array $params)
|
||||
{
|
||||
$this->params = $params;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set additional request attributes
|
||||
*
|
||||
* @access public
|
||||
* @param array $reqattrs
|
||||
* @return RequestBuilder
|
||||
*/
|
||||
public function withRequestAttributes(array $reqattrs)
|
||||
{
|
||||
$this->reqattrs = $reqattrs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the payload
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$payload = array_merge_recursive($this->reqattrs, array(
|
||||
'jsonrpc' => '2.0',
|
||||
'method' => $this->procedure,
|
||||
'id' => $this->id ?: mt_rand(),
|
||||
));
|
||||
|
||||
if (! empty($this->params)) {
|
||||
$payload['params'] = $this->params;
|
||||
}
|
||||
|
||||
return json_encode($payload);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user