diff --git a/src/GraphAxisGeneral.php b/src/GraphAxisGeneral.php new file mode 100644 index 0000000..ed62465 --- /dev/null +++ b/src/GraphAxisGeneral.php @@ -0,0 +1,492 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The general axis management */ +class GraphAxisGeneral +{ + /** The min value of the axis. Do not use it if the axis is composed of labels + */ + protected $min = null; + + /** The max value of the axis. Do not use it if the axis is composed of labels + */ + protected $max = null; + + /** The label max to display + */ + protected $labelMax; + + /** The label min to display + */ + protected $labelMin; + + /** The data displayed as values on the axis + */ + protected $data; + + /** Set if the axis is only numerical (true) or is composed of labels (false) + */ + protected $numerical; + + /** The minimum bottom position in pixels. Used on vertical axis + */ + protected $bottom; + + /** The maximum top position in pixels. Used on vertical axis + */ + protected $top; + + /** The minimum left position in pixels. Used on horizontal axis + */ + protected $left; + + /** The maximum right position in pixels. Used on horizontal axis + */ + protected $right; + + /** The fontfile to write the labels + */ + protected $fontfile; + + /** The fontsize to write the labels + */ + protected $fontsize = 8; + + /** Axis color + */ + protected $axisColor; + + /** Grid color on the axis + * Can be set to null or "transparent" to not display the grid + */ + protected $gridColor; + + /** Set the min value of the axis if the parameter is provided. + * Get the min value of the axis if the parameter is not provided + * @param integer|null $min The min value of the axis + */ + public function min($min = null) + { + if ($min === null) { + return $this->min; + } + if (! is_numeric($min)) { + throw new \Exception(dgettext( + "domframework", + "Invalid min provided to graph Axis" + ) . " " . get_class($this), 406); + } + $this->min = $min; + return $this; + } + + /** Set the max value of the axis if the parameter is provided. + * Get the max value of the axis if the parameter is not provided + * @param integer|null $max The max value of the axis + */ + public function max($max = null) + { + if ($max === null) { + return $this->max; + } + if (! is_numeric($max)) { + throw new \Exception(dgettext( + "domframework", + "Invalid max provided to graph Axis" + ) . " " . get_class($this), 406); + } + $this->max = $max; + return $this; + } + + /** Set the data of the axis if the parameter is provided. + * Get the data of the axis if the parameter is not provided + * @param array|null $data The data of the axis + */ + public function data($data = null) + { + if ($data === null) { + return $this->data; + } + if (! is_array($data)) { + throw new \Exception(dgettext( + "domframework", + "Invalid data provided to graph Axis" + ) . " " . get_class($this), 406); + } + $this->data = $data; + if ($this->numerical === null) { + // Look if the provided data are only numerical. Then define the numerical + // property + foreach ($data as $d) { + if (! is_numeric($d)) { + $this->numerical = false; + break; + } + } + if ($this->numerical === null) { + $this->numerical = true; + } + } + return $this; + } + + /** Set if the axis is numerical or composed of labels if the parameter is + * provided. + * Get if the axis is numerical if the parameter is not provided + * @param boolean|null $numerical the axis is numerical + */ + public function numerical($numerical = null) + { + if ($numerical === null) { + return $this->numerical; + } + if (! is_bool($numerical)) { + throw new \Exception(dgettext( + "domframework", + "Invalid numerical parameter provided to graph Axis" + ) . " " . + get_class($this), 406); + } + $this->numerical = $numerical; + return $this; + } + + /** Set the bottom position of the axis if the parameter is provided. + * Get the bottom position of the axis if the parameter is not provided + * @param integer|null $bottom The bottom position of the axis + */ + public function bottom($bottom = null) + { + if ($bottom === null) { + return $this->bottom; + } + if (! is_integer($bottom) || $bottom < 0 || $bottom > 5000) { + throw new \Exception(dgettext( + "domframework", + "Invalid bottom provided to graph Axis" + ) . " " . get_class($this), 406); + } + $this->bottom = $bottom; + return $this; + } + + /** Set the top position of the axis if the parameter is provided. + * Get the top position of the axis if the parameter is not provided + * @param integer|null $top The top position of the axis + */ + public function top($top = null) + { + if ($top === null) { + return $this->top; + } + if (! is_integer($top) || $top < 0 || $top > 5000) { + throw new \Exception(dgettext( + "domframework", + "Invalid top provided to graph Axis" + ) . " " . get_class($this), 406); + } + $this->top = $top; + return $this; + } + + /** Set the left position of the axis if the parameter is provided. + * Get the left position of the axis if the parameter is not provided + * @param integer|null $left The left position of the axis + */ + public function left($left = null) + { + if ($left === null) { + return $this->left; + } + if (! is_integer($left) || $left < 0 || $left > 5000) { + throw new \Exception(dgettext( + "domframework", + "Invalid left provided to graph Axis" + ) . " " . get_class($this), 406); + } + $this->left = $left; + return $this; + } + + /** Set the right position of the axis if the parameter is provided. + * Get the right position of the axis if the parameter is not provided + * @param integer|null $right The right position of the axis + */ + public function right($right = null) + { + if ($right === null) { + return $this->right; + } + if (! is_integer($right) || $right < 0 || $right > 5000) { + throw new \Exception(dgettext( + "domframework", + "Invalid right provided to graph Axis" + ) . " " . get_class($this), 406); + } + $this->right = $right; + return $this; + } + + /** Set the fontfile of the labels if the parameter is provided. + * Get the fontfile of the labels if the parameter is not provided + * @param string|null $fontfile The fontfile of the title + */ + public function fontfile($fontfile = null) + { + if ($fontfile === null) { + return $this->fontfile; + } + if ( + ! is_string($fontfile) || strlen($fontfile) < 0 || + ! file_exists($fontfile) || ! is_readable($fontfile) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid fontfile provided to graph title" + ), 406); + } + $this->fontfile = $fontfile; + return $this; + } + + /** Set the font size of the labels if the parameter is provided. + * Get the font size of the labels if the parameter is not provided + * @param integer|null $fontsize The font size of the title + */ + public function fontsize($fontsize = null) + { + if ($fontsize === null) { + return $this->fontsize; + } + if (! is_integer($fontsize) || $fontsize < 2 || $fontsize > 100) { + throw new \Exception(dgettext( + "domframework", + "Invalid fontsize provided to graph title" + ), 406); + } + $this->fontsize = $fontsize; + return $this; + } + + /** Set the axis color if the parameter is provided. + * Get the axis color if the parameter is not provided + * @param string|null $axisColor The axis color + */ + public function axisColor($axisColor = null) + { + if ($axisColor === null) { + return $this->axisColor; + } + if ( + ! is_string($axisColor) || + ! in_array($axisColor, Color::colorList()) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid axisColor provided to graph axis" + ), 406); + } + $this->axisColor = $axisColor; + return $this; + } + + /** Set the grid color if the parameter is provided. + * Get the grid color if the parameter is not provided + * @param string|null $gridColor The grid color + */ + public function gridColor($gridColor = null) + { + if ($gridColor === null) { + return $this->gridColor; + } + if ( + ! is_string($gridColor) || + ! in_array($gridColor, Color::colorList()) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid gridColor provided to graph grid" + ), 406); + } + $this->gridColor = $gridColor; + return $this; + } + + /** Calculate the labels that will be displayed on the axis + * @param integer $nbMaxValues The maximum number of values to return + * @return array The array of labels to display + */ + protected function labels($nbMaxValues) + { + // Activate the debug of this method + $deb = false; + if (! is_int($nbMaxValues)) { + throw new \Exception( + "Invalid parameter nbMaxValues provided to graphAxisGeneral::labels", + 500 + ); + } + if ($deb) { + echo "=========== LABELS\n"; + } + if ($this->min > 0) { + $minLabel = $this->min * 0.90; + } else { + $minLabel = $this->min * 1.05; + } + if ($this->max > 0) { + $maxLabel = $this->max * 1.05; + } else { + $maxLabel = $this->max * 0.90; + } + + $minBase = intval(log10($minLabel)) - 1; + $maxBase = intval(log10($maxLabel)) - 1; + $base = max($minBase, $maxBase); + if ($deb) { + echo "minBase=$minBase, maxBase=$maxBase ===> base = $base\n"; + } + + if ($base < 0 && $base > -1) { + $base = $base - 1; + } + // The while loop reduce the base to not explode the number of labels + // If there is too much labels, remove one digit and retry + while (1) { + $this->labelMin = null; + $this->labelMax = null; + $min = round($minLabel, -1 * $base); + $max = round($maxLabel, -1 * $base); + if ($deb) { + echo "min = $min, max = $max\n"; + } + + $scale = pow(10, $base); + if ($deb) { + echo "BASE=" . ($base) . ", SCALE = $scale\n"; + } + if ($scale === 0 || $scale === 0.0) { + die("Scale equal 0 on line " . __LINE__ . "\n"); + } + $labels = array(); + if ($this->min <= 0 && $this->max >= 0) { + // Return the 0 label and the values arround it + $zeroAlreadyDraw = false; + for ( + $i = 0; + \bccomp($i, 1.2 * $max, abs($base) + 1) < 1; + $i += $scale + ) { + if ($i === 0) { + $zeroAlreadyDraw = true; + } + if ($deb) { + echo "Values with Zero : add pos val $i\n"; + } + $labels[] = $i; + } + for ( + $i = 0; + \bccomp($i, 1.4 * $min, abs($base) + 1) >= 0; + $i -= $scale + ) { + if ($zeroAlreadyDraw === true && $i === 0) { + continue; + } + if ($deb) { + echo "Values with Zero : add neg val $i\n"; + } + $labels[] = $i; + } + } else { + // No 0 Axis : From $this->min to $this->max + for ( + $i = $min; + \bccomp($i, $max, abs($base) + 1) < 1; + $i += $scale + ) { + if ($deb) { + echo "Values no Zero : add val $i\n"; + } + $labels[] = $i; + } + } + if (count($labels) <= $nbMaxValues) { + foreach ($labels as &$label) { + if ($base < 0) { + $label = sprintf("%0." . intval(abs($base)) . "f", $label); + } else { + $label = sprintf("%" . intval($base) . "d", $label); + } + if ( + $this->labelMin === null || + \bccomp($this->labelMin, $label, abs($base) + 1) >= 0 + ) { + $this->labelMin = $label; + } + if ( + $this->labelMax === null || + \bccomp($this->labelMax, $label, abs($base) + 1) <= 0 + ) { + $this->labelMax = $label; + } + } + + if (\bccomp($this->labelMin, $this->min, abs($base) + 1) > 0) { + // Add a label in the minimum + $this->labelMin = $this->labelMin - $scale; + if ($deb) { + echo "Force Add Min $this->labelMin\n"; + } + if ($base < 0) { + $labels[] = sprintf( + "%0." . intval(abs($base)) . "f", + $this->labelMin + ); + } else { + $labels[] = sprintf("%" . intval($base) . "d", $this->labelMin); + } + } + if (\bccomp($this->labelMax, $this->max, abs($base) + 1) < 0) { + $this->labelMax = $this->labelMax + $scale; + if ($deb) { + echo "Force Add Max $this->labelMax\n"; + } + if ($base < 0) { + $labels[] = sprintf( + "%0." . intval(abs($base)) . "f", + $this->labelMax + ); + } else { + $labels[] = sprintf("%" . intval($base) . "d", $this->labelMax); + } + } + if ($this->labelMin > $this->min) { + die("labelMin > min ($this->labelMin > $this->min)\n"); + } + if ($this->labelMax < $this->max) { + die("labelMax < max ($this->labelMax < $this->max)\n"); + } + if (count($labels) <= $nbMaxValues) { + $labels = array_unique($labels); + if ($deb) { + print_r($labels); + } + return $labels; + } + } + if ($deb) { + echo "LOOP == " . count($labels) . " > $nbMaxValues ==========\n"; + } + $base = $base + 0.1; + } + } +} diff --git a/src/GraphAxisHorizontal.php b/src/GraphAxisHorizontal.php new file mode 100644 index 0000000..af7f089 --- /dev/null +++ b/src/GraphAxisHorizontal.php @@ -0,0 +1,111 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The graph Axis Horizontal class */ +class GraphAxisHorizontal extends GraphAxisGeneral +{ + /** Calculate the position in pixels for a value + * If the value is out of range, return null to not draw the point + * @param string|float|integer $value The value to position + */ + public function position($value) + { + if ($value === null) { + return null; + } + if (! is_numeric($value) && ! is_string($value)) { + throw new \Exception(dgettext( + "domframework", + "Invalid value provided to graph Axis for position" + ) . " " . + get_class($this), 406); + } + if ($this->numerical === null) { + throw new \Exception(dgettext( + "domframework", + "No numerical type defined for Axis" + ) . " " . get_class($this), 406); + } + if ($this->numerical) { + // Numerical axis, use a standard scale + if ($value < $this->min || $value > $this->max) { + return null; + } + if (($this->max - $this->min) == 0) { + $dividor = 1; + } else { + $dividor = ($this->max - $this->min); + } + $scale = ($value - $this->min) / $dividor; + return intval($this->left + $scale * ($this->right - $this->left)); + } else { + // Label axis, count them + if (! is_array($this->data)) { + throw new \Exception(dgettext( + "domframework", + "No data defined for Axis" + ) . " " . get_class($this), 406); + } + $pos = array_search($value, $this->data); + if ($pos === false) { + return null; + } + $width = ($this->right - $this->left) / count($this->data); + return intval($this->left + $width * $pos + $width / 2); + } + } + + /** Calculate the positionMin, used for labeled axies + * If the value is out of range, return null to not draw the point + * @param string|float|integer $value The value to position + */ + public function positionMin($value) + { + if ($this->numerical) { + return $posCenter; + } + if (! is_array($this->data)) { + throw new \Exception(dgettext( + "domframework", + "No data defined for Axis" + ) . " " . get_class($this), 406); + } + $pos = array_search($value, $this->data); + if ($pos === false) { + return null; + } + $width = ($this->right - $this->left) / count($this->data); + return intval($this->left + $width * $pos); + } + + /** Calculate the positionMax, used for labeled axies + * If the value is out of range, return null to not draw the point + * @param string|float|integer $value The value to position + */ + public function positionMax($value) + { + $posCenter = $this->position($value); + if ($this->numerical) { + return $posCenter; + } + if (! is_array($this->data)) { + throw new \Exception(dgettext( + "domframework", + "No data defined for Axis" + ) . " " . get_class($this), 406); + } + $pos = array_search($value, $this->data); + if ($pos === false) { + return null; + } + $width = ($this->right - $this->left) / count($this->data); + return intval($this->left + $width * $pos + $width); + } +} diff --git a/src/GraphAxisVertical.php b/src/GraphAxisVertical.php new file mode 100644 index 0000000..766198c --- /dev/null +++ b/src/GraphAxisVertical.php @@ -0,0 +1,207 @@ + + * @license BSD + */ + +namespace Domframework; + +/** Manage the vertical axis */ +class GraphAxisVertical extends GraphAxisGeneral +{ + /** The angle choosed to draw the graph + */ + private $angle; + + /** The padding between the label and the axis + */ + protected $padding = 7; + + /** The width of the labels + padding (it is the base of the graph) in pixels + */ + protected $width; + + /** The number of chars to be displayed in one label + */ + protected $nbcharsLabel = 0; + + /** Look for the width of the Y axis + * @param resource $gd The resource to modify + */ + public function getWidth($gd) + { + if ($this->numerical) { + if ($this->max === null) { + throw new \Exception(dgettext( + "domframework", + "The max is not defined for graphAxisVertical" + ), 406); + } + // Look at the minimum distance between two labeled values + $bbox = imagettfbbox($this->fontsize, 0, $this->fontfile, "NOT"); + $height = abs($bbox[5] - $bbox[1]) + $this->padding; + for ($nbMaxValues = 10; $nbMaxValues > 2; $nbMaxValues--) { + if ($nbMaxValues * $height < abs($this->top - $this->bottom)) { + break; + } + } + $labels = $this->labels($nbMaxValues); + $labelBiggest = ""; + $this->nbcharsLabel = 0; + foreach ($labels as $label) { + if (strlen($label) > $this->nbcharsLabel) { + $this->nbcharsLabel = strlen($label); + $labelBiggest = $label; + } + } + $bbox = imagettfbbox( + $this->fontsize, + $this->angle, + $this->fontfile, + $labelBiggest + ); + $width = abs($bbox[4] - $bbox[0]); + $this->width = $width + 2 * $this->padding; + return $this->width; + } else { + if ($this->data === null) { + return $this->padding; + } + die("TODO : getWidth in labeled line " . __LINE__ . "\n"); + } + } + + /** Calculate the position in pixels for a value + * If the value is out of range, return null to not draw the point + * @param string|float|integer $value The value to position + */ + public function position($value) + { + if ($value === null) { + return null; + } + if (! is_numeric($value) && ! is_string($value)) { + throw new \Exception(dgettext( + "domframework", + "Invalid value provided to graph Axis for position" + ) . " " . + get_class($this), 406); + } + if ($this->numerical === null) { + throw new \Exception(dgettext( + "domframework", + "No numerical type defined for Axis" + ) . " " . get_class($this), 406); + } + if ($this->numerical) { + // Numerical axis, use a standard scale + if ($value > $this->labelMax || $value < $this->labelMin) { + return null; + } + $scale = ($value - $this->labelMin) / ($this->labelMax - $this->labelMin); + $pos = intval($this->bottom + $scale * ($this->top - $this->bottom)); + return $pos; + } else { + // Label axis, count them + if (! is_array($this->data)) { + throw new \Exception(dgettext( + "domframework", + "No data defined for Axis" + ) . " " . get_class($this), 406); + } + $pos = array_search($value, $this->data); + if ($pos === false) { + return null; + } + $width = ($this->top - $this->bottom) / count($this->data); + $pos = intval($this->bottom + $width * $pos + $width / 2); + return $pos; + } + } + + /** Calculate the positionMin, used for labeled axies + * If the value is out of range, return null to not draw the point + * @param string|float|integer $value The value to position + */ + public function positionMin($value) + { + if ($this->numerical) { + return $posCenter; + } + if (! is_array($this->data)) { + throw new \Exception(dgettext( + "domframework", + "No data defined for Axis" + ) . " " . get_class($this), 406); + } + $pos = array_search($value, $this->data); + if ($pos === false) { + return null; + } + $width = ($this->top - $this->bottom) / count($this->data); + return intval($this->bottom + $width * $pos); + } + + /** Calculate the positionMax, used for labeled axies + * If the value is out of range, return null to not draw the point + * @param string|float|integer $value The value to position + */ + public function positionMax($value) + { + $posCenter = $this->position($value); + if ($this->numerical) { + return $posCenter; + } + if (! is_array($this->data)) { + throw new \Exception(dgettext( + "domframework", + "No data defined for Axis" + ) . " " . get_class($this), 406); + } + $pos = array_search($value, $this->data); + if ($pos === false) { + return null; + } + $width = ($this->top - $this->bottom) / count($this->data); + return intval($this->bottom + $width * $pos + $width); + } + + /** Draw the axis labels and lines + * @param resource $gd The resource to modify + */ + public function draw($gd) + { + $axisColor = Color::allocateFromText($gd, $this->axisColor); + if ($this->numerical) { + // Look at the minimum distance between two labeled values + $bbox = imagettfbbox($this->fontsize, 0, $this->fontfile, "NOT"); + $height = abs($bbox[5] - $bbox[1]) + $this->padding; + for ($nbMaxValues = 10; $nbMaxValues > 2; $nbMaxValues--) { + if ($nbMaxValues * $height < abs($this->top - $this->bottom)) { + break; + } + } + + $width = $this->getWidth($gd); + $labels = $this->labels($nbMaxValues); + if (count($labels) > $nbMaxValues) { + die("Too much labels to display (" . + count($labels) . " > $nbMaxValues)\n"); + } + foreach ($labels as $label) { + $this->drawOne($gd, $width, $label); + $this->drawGrid($gd, $width, $label); + } + $this->drawAxis($gd, $width); + } else { + // Labeled + // If there is no data, there is nothing to draw : return + if ($this->data === null) { + return; + } + die("graphAxisVertical::draw labeled TBD line " . __LINE__ . "\n"); + } + } +} diff --git a/src/GraphAxisX.php b/src/GraphAxisX.php new file mode 100644 index 0000000..cf07f2b --- /dev/null +++ b/src/GraphAxisX.php @@ -0,0 +1,214 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The X axis management */ +class GraphAxisX extends GraphAxisHorizontal +{ + /** The angle choosed to draw the graph + */ + private $angle; + + /** The padding between the label and the axis + */ + private $padding = 7; + + /** The heigth of the labels + padding (it is the base of the graph) in pixels + */ + private $height; + + /** The number of chars to be displayed in one label + */ + protected $nbcharsLabel = 0; + + /** Look for the height of the X axis based on the angle of the text when it + * will be drawn + * @param resource $gd The resource to modify + */ + public function getHeight($gd) + { + // Look for the angle of the value. Start Horizontally (angle=0), then + // try 45°, then finish at 90°. As all the values must in the same angle, + // test all the values. If one is bad, change the angle and retry all the + // values + $width = null; + if ($this->numerical) { + if ($this->max === null) { + throw new \Exception(dgettext( + "domframework", + "The max is not defined for graphAxisHorizontal" + ), 406); + } + $powMax = intval(log10($this->max)); + $tenPercent = round($this->max / pow(10, $powMax), 1) + 0.1; + $labelMax = $tenPercent * pow(10, $powMax); + $this->nbcharsLabel = strlen($labelMax); + $bbox = imagettfbbox( + $this->fontsize, + $this->angle, + $this->fontfile, + $labelMax + ); + $height = abs($bbox[4] - $bbox[0]); + $this->height = $height + 2 * $this->padding; + return $this->height; + } else { + if ($this->data === null) { + return 0; + } + foreach (array(0, 45, 90) as $this->angle) { + $bboxMaxHeight = 0; + foreach ($this->data as $key => $value) { + if ($width === null) { + $width = $this->positionMax($value) - $this->positionMin($value); + } + // Look for the bounding box around the text. The bounding is not + // write on the image and return the coordinates for the text box + $bbox = imagettfbbox( + $this->fontsize, + $this->angle, + $this->fontfile, + $value + ); + if (abs($bbox[4] - $bbox[0]) > $width) { + continue 2; + } + $bboxMaxHeight = max($bboxMaxHeight, abs($bbox[5] - $bbox[1])); + } + // All the values are OK : we have found the angle : break the angle + // loop + break; + } + } + $this->height = $bboxMaxHeight + 2 * $this->padding; + return $bboxMaxHeight + 2 * $this->padding; + } + + /** Draw the axis + * @param resource $gd The resource to modify + */ + public function draw($gd) + { + $axisColor = Color::allocateFromText($gd, $this->axisColor); + if ($this->numerical) { + if ($this->angle === null) { + $this->getHeight($gd); + } + if ($this->data === null) { + return; + } + foreach ($this->data as $key => $value) { + $position = $this->position($value); + + // Draw the labels + $bbox = imagettfbbox( + $this->fontsize, + $this->angle, + $this->fontfile, + $value + ); + $width = abs($bbox[4] - $bbox[0]); + $x = $position - $width / 2; + $y = $this->bottom - $this->padding; + // The font color is forced to black + imagettftext( + $gd, + $this->fontsize, + $this->angle, + $x, + $y, + Color::allocateFromText($gd, "black"), + $this->fontfile, + $value + ); + + // Draw the scale + imageline( + $gd, + $position, + $this->bottom - $this->height + 1, + $position, + $this->bottom - $this->height + 1 + $this->padding, + $axisColor + ); + + // Draw the grid + $this->drawGrid($gd, $this->bottom - $this->height, $position); + } + + // Draw the axis + $y = $this->bottom - $this->height + 1; + imageline($gd, $this->left, $y, $this->right, $y, $axisColor); + } else { + if ($this->angle === null) { + $this->getHeight($gd); + } + if ($this->data === null) { + return; + } + foreach ($this->data as $key => $value) { + $position = $this->position($value); + + // Draw the labels + $bbox = imagettfbbox( + $this->fontsize, + $this->angle, + $this->fontfile, + $value + ); + $width = abs($bbox[4] - $bbox[0]); + $x = $position - $width / 2; + $y = $this->bottom - $this->padding; + // The font color is forced to black + imagettftext( + $gd, + $this->fontsize, + $this->angle, + $x, + $y, + Color::allocateFromText($gd, "black"), + $this->fontfile, + $value + ); + + // Draw the separators + $y = $this->bottom - $this->height + 1; + $xmin = $this->positionMin($value); + $xmax = $this->positionMax($value); + imageline($gd, $xmin, $y, $xmin, $y + $this->padding, $axisColor); + imageline($gd, $xmax, $y, $xmax, $y + $this->padding, $axisColor); + + // Draw the grid + $this->drawGrid($gd, $this->bottom - $this->height, $position); + } + + // Draw the axis + $y = $this->bottom - $this->height + 1; + imageline($gd, $this->left, $y, $this->right, $y, $axisColor); + } + } + + /** Draw the grid + * @param resource $gd The resource to modify + * @param integer $width The width of the labels on the axis + * @param integer|float $position The position to draw + */ + protected function drawGrid($gd, $width, $position) + { + if ($this->gridColor === null || $this->gridColor === "transparent") { + return; + } + $gridColor = Color::allocateFromText($gd, $this->gridColor); + if ($position === null) { + return; + } + $y = $this->bottom - $this->height; + imageline($gd, $position, $y, $position, $this->top, $gridColor); + } +} diff --git a/src/GraphAxisY1.php b/src/GraphAxisY1.php new file mode 100644 index 0000000..8b636a9 --- /dev/null +++ b/src/GraphAxisY1.php @@ -0,0 +1,94 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The Y1 axis management */ +class GraphAxisY1 extends GraphAxisVertical +{ + /** Draw one value on the axis + * @param resource $gd The resource to modify + * @param integer $width The width of the labels on the axis + * @param integer|float $val The value to draw + */ + protected function drawOne($gd, $width, $val) + { + $axisColor = Color::allocateFromText($gd, $this->axisColor); + if ($this->numerical) { + // Do not allow the label to be longer than $this->max + if (strlen($val) > $this->nbcharsLabel) { + $itmp = rtrim(substr($val, 0, $this->nbcharsLabel), "."); + } else { + $itmp = $val; + } + // Write the labels align to right + $bbox = imagettfbbox($this->fontsize, 0, $this->fontfile, $itmp); + $labelwidth = abs($bbox[4] - $bbox[0]) + $this->padding; + $labelheight = abs($bbox[5] - $bbox[1]); + $y = $this->position($val); + if ($y === null) { + return; + } + // The color is forced to black + imagettftext( + $gd, + $this->fontsize, + 0, + $width - $labelwidth, + $y + $labelheight / 2, + Color::allocateFromText($gd, "black"), + $this->fontfile, + $itmp + ); + + // Display the separators + imageline( + $gd, + $width - $this->padding, + $y, + $width, + $y, + $axisColor + ); + } else { + die("graphAxisY1:: drawOne NOT numerical line " . __LINE__ . "\n"); + } + } + + /** Draw the axis + * @param resource $gd The resource to modify + * @param integer $width The width of the labels on the axis + */ + protected function drawAxis($gd, $width) + { + $axisColor = Color::allocateFromText($gd, $this->axisColor); + imageline($gd, $width, $this->bottom, $width, $this->top, $axisColor); + } + + /** Draw the grid + * @param resource $gd The resource to modify + * @param integer $width The width of the labels on the axis + * @param integer|float $val The value to draw + */ + protected function drawGrid($gd, $width, $val) + { + if ($this->gridColor === null || $this->gridColor === "transparent") { + return; + } + $gridColor = Color::allocateFromText($gd, $this->gridColor); + if ($this->numerical) { + $y = $this->position($val); + if ($y === null) { + return; + } + imageline($gd, $width, $y, $this->right, $y, $gridColor); + } else { + die("graphAxisY1:: drawGrid NOT numerical line " . __LINE__ . "\n"); + } + } +} diff --git a/src/GraphAxisY2.php b/src/GraphAxisY2.php new file mode 100644 index 0000000..193f2b8 --- /dev/null +++ b/src/GraphAxisY2.php @@ -0,0 +1,90 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The Y2 axis management */ +class GraphAxisY2 extends GraphAxisVertical +{ + /** Draw one value on the axis + * @param resource $gd The resource to modify + * @param integer $width The width of the labels on the axis + * @param integer|float $val The value to draw + */ + protected function drawOne($gd, $width, $val) + { + $axisColor = Color::allocateFromText($gd, $this->axisColor); + if ($this->numerical) { + // Do not allow the label to be longer than $this->max + if (strlen($val) > $this->nbcharsLabel) { + $itmp = rtrim(substr($val, 0, $this->nbcharsLabel), "."); + } else { + $itmp = $val; + } + // Write the labels align to left + $bbox = imagettfbbox($this->fontsize, 0, $this->fontfile, $itmp); + $labelwidth = abs($bbox[4] - $bbox[0]) + $this->padding; + $labelheight = abs($bbox[5] - $bbox[1]); + $y = $this->position($val); + if ($y === null) { + return; + } + // The color is forced to black + imagettftext( + $gd, + $this->fontsize, + 0, + $this->left + $this->padding + 3, + $y + $labelheight / 2, + Color::allocateFromText($gd, "black"), + $this->fontfile, + $itmp + ); + + // Display the separators + imageline( + $gd, + $this->left, + $y, + $this->left + $this->padding, + $y, + $axisColor + ); + } else { + die("graphAxisY2:: drawOne NOT numerical line " . __LINE__ . "\n"); + } + } + + /** Draw the axis + * @param resource $gd The resource to modify + * @param integer $width The width of the labels on the axis + */ + protected function drawAxis($gd, $width) + { + $axisColor = Color::allocateFromText($gd, $this->axisColor); + imageline( + $gd, + $this->left, + $this->bottom, + $this->left, + $this->top, + $axisColor + ); + } + + /** Draw the grid + * @param resource $gd The resource to modify + * @param integer $width The width of the labels on the axis + * @param integer|float $val The value to draw + */ + protected function drawGrid($gd, $width, $val) + { + // The grid can't be graphed for Y2 axis : only the Y1 axis is allowed + return; + } +} diff --git a/src/GraphData.php b/src/GraphData.php new file mode 100644 index 0000000..edee1b6 --- /dev/null +++ b/src/GraphData.php @@ -0,0 +1,305 @@ + + * @license BSD + */ + +namespace Domframework; + +/** Read the data */ +class GraphData +{ + /** Store the data when the user provided them. Store them in array form + */ + private $data; + + /** The titles are on the first line + */ + private $titlesOnFirstLine = null; + + /** The titles are on the first column + */ + private $titlesOnFirstColumn = null; + + /** The data are stored horizontally + */ + private $horizontalData = null; + + /** Get the data from an indexed array + * @param array $array The data array to graph + */ + public function arrayIndexed($array) + { + $this->data = $array; + if (! is_array($this->data)) { + throw new \Exception( + dgettext( + "domframework", + "Invalid Array Parameter provided: not an array" + ), + 406 + ); + } + return $this; + } + + /** Get the data from an associative array + * The associative array are provided by database results + * @param array $array The data array to graph + */ + public function arrayAssociative($array) + { + if (! is_array($array)) { + throw new \Exception( + dgettext( + "domframework", + "Invalid Array Parameter provided: not an array" + ), + 406 + ); + } + $titles = array(); + $this->data = array(); + foreach ($array as $line => $lineArr) { + foreach ($lineArr as $key => $cell) { + $titles[$key] = ""; + $this->data[$line][] = $cell; + } + } + array_unshift($this->data, array_keys($titles)); + return $this; + } + + /** Get the data from a CSV string + * @param string $csv The CSV string + */ + public function csv($csv) + { + $csv = trim($csv); + $lines = preg_split('/( *\R)+/s', $csv); + $this->data = array_map('str_getcsv', $lines); + if (! is_array($this->data)) { + throw new \Exception( + dgettext( + "domframework", + "Invalid CSV provided: not converted to array" + ), + 406 + ); + } + return $this; + } + + /** Get the data from a JSON string + * @param string $json The JSON string + */ + public function json($json) + { + $this->data = json_decode($json, true); + if (! is_array($this->data)) { + throw new \Exception( + dgettext( + "domframework", + "Invalid JSON provided: not converted to array" + ), + 406 + ); + } + return $this; + } + + /** Titles on first line + * Set the value if the parameter is provided, get the value if the parameter + * is not set + * @param boolean|null $titlesOnFirstLine The titles on first line + */ + public function titlesOnFirstLine($titlesOnFirstLine = null) + { + if ($titlesOnFirstLine === null) { + return $this->titlesOnFirstLine; + } + if (! is_bool($titlesOnFirstLine)) { + throw new \Exception( + dgettext( + "domframework", + "Invalid titlesOnFirstLine provided to graph titlesOnFirstLine" + ), + 406 + ); + } + $this->titlesOnFirstLine = $titlesOnFirstLine; + return $this; + } + + /** Titles on first column + * Set the value if the parameter is provided, get the value if the parameter + * is not set + * @param boolean|null $titlesOnFirstColumn The titles on first column + */ + public function titlesOnFirstColumn($titlesOnFirstColumn = null) + { + if ($titlesOnFirstColumn === null) { + return $this->titlesOnFirstColumn; + } + if (! is_bool($titlesOnFirstColumn)) { + throw new \Exception( + dgettext( + "domframework", + "Invalid titlesOnFirstColumn provided to graph titlesOnFirstColumn" + ), + 406 + ); + } + $this->titlesOnFirstColumn = $titlesOnFirstColumn; + return $this; + } + + /** The data are stored horizontally in the array + * Set the value if the parameter is provided, get the value if the parameter + * is not set + * @param boolean|null $horizontalData The data are stored horizontally + */ + public function horizontalData($horizontalData = null) + { + if ($horizontalData === null) { + return $this->horizontalData; + } + if (! is_bool($horizontalData)) { + throw new \Exception( + dgettext( + "domframework", + "Invalid horizontalData provided to graph horizontalData" + ), + 406 + ); + } + $this->horizontalData = $horizontalData; + return $this; + } + + /** Get the series in an array with the associated values + */ + public function getSeries() + { + // 0. If there is no data to graph, nothing to do + if (count($this->data) === 0) { + return array(); + } + + // 1. If $this->titlesOnFirstLine === null, look if the first line contains + // titles + if ($this->titlesOnFirstLine === null) { + $this->titlesOnFirstLine = true; + if (count($this->data) === 1) { + $this->titlesOnFirstLine = false; + } + if (is_array($this->data[0])) { + foreach ($this->data[0] as $cell) { + if (is_numeric($cell)) { + $this->titlesOnFirstLine = false; + break; + } + } + } else { + $this->titlesOnFirstLine = false; + } + } + + // 2. If $this->titlesOnFirstColumn === null, look if the first column + // contains titles + if ($this->titlesOnFirstColumn === null) { + if (count($this->data[0]) === 1) { + $this->titlesOnFirstColumn = false; + } else { + $this->titlesOnFirstColumn = true; + foreach ($this->data as $lineArr) { + if (is_numeric($lineArr[0])) { + $this->titlesOnFirstColumn = false; + break; + } + } + } + } + + // 3. If $this->horizontalData === null, look for orientation with the + // titles states + if ($this->horizontalData === null) { + if ( + $this->titlesOnFirstColumn === true || + ! array_key_exists(1, $this->data) + ) { + $this->horizontalData = true; + } else { + $this->horizontalData = false; + } + } + + // 4. Create the series + $colTitles = array(); + $lineTitles = array(); + $series = array(); + foreach ($this->data as $linePos => $lineArr) { + if (! is_array($lineArr)) { + $lineArr = array($lineArr); + } + if ($linePos === 0) { + if ($this->titlesOnFirstLine) { + $colTitles = $lineArr; + if ($this->horizontalData === false) { + // First line with titles and vertical data: get the series names + $series = array_flip($lineArr); + foreach ($series as &$value) { + $value = array(); + } + if ($this->titlesOnFirstColumn) { + array_shift($series); + } + } + continue; + } else { + foreach ($lineArr as $i => $value) { + $colTitles[$i] = $i; + } + } + } elseif (count($lineArr) !== count($colTitles)) { + throw new \Exception(sprintf( + dgettext( + "domframework", + "Invalid data provided: line %d doesn't have the same number " . + "of elements as the first line (%d != %d elements)" + ), + $linePos + 1, + count($lineArr), + count($colTitles) + ), 406); + } + foreach ($lineArr as $colPos => $cell) { + $cell = trim($cell); + if ($colPos === 0) { + if ($this->titlesOnFirstColumn) { + $lineTitles[$linePos] = $cell; + if ($this->horizontalData === true) { + $series[$cell] = array(); + } + continue; + } else { + $lineTitles[$linePos] = $linePos; + } + } + if (! is_numeric($cell)) { + $cell = null; + } else { + $cell = $cell + 0.0; + } + if ($this->horizontalData === false) { + $series[$colTitles[$colPos]][$lineTitles[$linePos]] = $cell; + } else { + $series[$lineTitles[$linePos]][$colTitles[$colPos]] = $cell; + } + } + } + return $series; + } +} diff --git a/src/GraphLegend.php b/src/GraphLegend.php new file mode 100644 index 0000000..d250061 --- /dev/null +++ b/src/GraphLegend.php @@ -0,0 +1,271 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The graphLegend object */ +class GraphLegend +{ + /** Show the legend (no legend by default) + */ + private $show = false; + + /** The TTF fontfile to use + */ + private $fontfile = null; + + /** The font size to use + */ + private $fontsize = 10; + + /** The legend color for the font + */ + private $color = "black"; + + /** The legend background-color + */ + private $bgcolor = "white"; + + /** The legend border color + */ + private $borderColor = "black"; + + /** The padding arround the title (in px) + */ + private $padding = 10; + + /** Set the legend display status if the parameter is provided. + * Get the legend display status if the parameter is not provided + * @param boolean|null $show The legend display status + */ + public function show($show = null) + { + if ($show === null) { + return $this->show; + } + if (! is_bool($show)) { + throw new \Exception(dgettext( + "domframework", + "Invalid show value provided to graph legend" + ), 406); + } + $this->show = $show; + return $this; + } + + /** Set the fontfile of the legend if the parameter is provided. + * Get the fontfile of the legend if the parameter is not provided + * @param string|null $fontfile The fontfile of the legend + */ + public function fontfile($fontfile = null) + { + if ($fontfile === null) { + return $this->fontfile; + } + if ( + ! is_string($fontfile) || strlen($fontfile) < 0 || + ! file_exists($fontfile) || ! is_readable($fontfile) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid fontfile provided to graph legend" + ), 406); + } + $this->fontfile = $fontfile; + return $this; + } + + /** Set the font size of the legend if the parameter is provided. + * Get the font size of the legend if the parameter is not provided + * @param integer|null $fontsize The font size of the legend + */ + public function fontsize($fontsize = null) + { + if ($fontsize === null) { + return $this->fontsize; + } + if (! is_integer($fontsize) || $fontsize < 2 || $fontsize > 100) { + throw new \Exception(dgettext( + "domframework", + "Invalid fontsize provided to graph legend" + ), 406); + } + $this->fontsize = $fontsize; + return $this; + } + + /** Set the color of the legend if the parameter is provided. + * Get the color of the legend if the parameter is not provided + * @param string|null $color The color of the legend + */ + public function color($color = null) + { + if ($color === null) { + return $this->color; + } + if ( + ! is_string($color) || + ! in_array($color, Color::colorList()) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid color provided to graph legend" + ), 406); + } + $this->color = $color; + return $this; + } + + /** Set the background-color of the legend if the parameter is provided. + * Get the background-color of the legend if the parameter is not provided + * @param string|null $bgcolor The background-color of the legend + */ + public function bgcolor($bgcolor = null) + { + if ($bgcolor === null) { + return $this->bgcolor; + } + if ( + ! is_string($bgcolor) || + ! in_array($bgcolor, Color::colorList()) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid bgcolor provided to graph legend" + ), 406); + } + $this->bgcolor = $bgcolor; + return $this; + } + + /** Set the border color of the legend if the parameter is provided. + * Get the border color of the legend if the parameter is not provided + * @param string|null $borderColor The border color of the legend + */ + public function borderColor($borderColor = null) + { + if ($borderColor === null) { + return $this->borderColor; + } + if ( + ! is_string($borderColor) || + ! in_array($borderColor, Color::colorList()) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid borderColor provided to graph legend" + ), 406); + } + $this->borderColor = $borderColor; + return $this; + } + + /** Set the padding of the legend if the parameter is provided. + * Get the padding of the legend if the parameter is not provided + * @param integer|null $padding The padding of the legend + */ + public function padding($padding = null) + { + if ($padding === null) { + return $this->padding; + } + if (! is_integer($padding) || $padding < 0 || $padding > 200) { + throw new \Exception(dgettext( + "domframework", + "Invalid padding provided to graph legend" + ), 406); + } + $this->padding = $padding; + return $this; + } + + /** Draw the legend in the $gd resource provided + * @param resource $gd The resource to modify + * @param array $free The free space coordinates on the graphic + * @param object $series The series to graph + * @return array the new free coordinates array + */ + public function draw($gd, $free, $series) + { + if ($this->show === false) { + return $free; + } + // Look for maxmimum width of the labels + $maxwidth = 0; + $height = 0; + foreach ($series->getList() as $number => $serie) { + $bbox = imagettfbbox($this->fontsize, 0, $this->fontfile, $serie); + $width = abs($bbox[4] - $bbox[0]); + // TODO : If the serie name is too long, split it ! + // 20px for the sample + the space before the label + if ((20 + $maxwidth) > (($free[2] - $free[0]) / 2)) { + throw new \Exception( + dgettext( + "domframework", + "The serie name in legend must not takes more than half of the graph" + ), + 500 + ); + } + if ($number > 0) { + $height += $this->padding; + } + $height += abs($bbox[5] - $bbox[1]); + $maxwidth = max($maxwidth, $width); + } + + $x1 = $free[2] - $maxwidth - 20 - $this->padding * 2; + $x2 = $free[2] - $this->padding; + $y1 = $free[1] + $this->padding; + $y2 = $y1 + $height + $this->padding; + + // Size of the border : 1px + $border = 1; + // Draw the background rectangle + imagefilledrectangle( + $gd, + $x1, + $y1, + $x2, + $y2, + Color::allocateFromText($gd, $this->borderColor) + ); + imagefilledrectangle( + $gd, + $x1 + $border, + $y1 + $border, + $x2 - $border, + $y2 - $border, + Color::allocateFromText($gd, $this->bgcolor) + ); + + // Display the serie names + $y = $y1; + foreach ($series->getList() as $number => $serie) { + // Write the label + $bbox = imagettfbbox($this->fontsize, 0, $this->fontfile, $serie); + $height = abs($bbox[5] - $bbox[1]); + $y += $height; + imagettftext( + $gd, + $this->fontsize, + 0, + $x1 + 30, + intval($y + $height / 2), + Color::allocateFromText($gd, $this->color), + $this->fontfile, + $serie + ); + // Draw the sample + $series->serie($serie)->style()->sample($gd, $x1 + 15, $y); + $y += $this->padding; + } + $free = array($free[0], $free[1], $x1 - 5, $free[3]); + return $free; + } +} diff --git a/src/GraphPalette.php b/src/GraphPalette.php new file mode 100644 index 0000000..623b8e9 --- /dev/null +++ b/src/GraphPalette.php @@ -0,0 +1,40 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The graphPalette class */ +class GraphPalette +{ + /** Get the complete palette + * @param string $name The palette name to get + */ + public static function getPalette($name) + { + $palette = array( + "basic" => array( + array("bgcolor" => "indianred", "color" => "firebrick"), + array("bgcolor" => "lightblue", "color" => "blue"), + array("bgcolor" => "peru", "color" => "maroon"), + array("bgcolor" => "mediumaquamarine", "color" => "teal"), + array("bgcolor" => "orange", "color" => "goldenrod"), + array("bgcolor" => "limegreen", "color" => "green"), + array("bgcolor" => "darkgrey", "color" => "grey"), + array("bgcolor" => "darkyellow1", "color" => "darkyellow2"), + array("bgcolor" => "grey", "color" => "black"), + ), + ); + if (! is_string($name) || ! array_key_exists($name, $palette)) { + throw new \Exception(dgettext( + "domframework", + "Unknown palette name provided" + ), 406); + } + return $palette[$name]; + } +} diff --git a/src/GraphSerie.php b/src/GraphSerie.php new file mode 100644 index 0000000..e9efbaa --- /dev/null +++ b/src/GraphSerie.php @@ -0,0 +1,307 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The serie object */ +class GraphSerie +{ + /** The name of the serie + */ + private $name; + + /** The data values for the serie + */ + private $data; + + /** The numericalKey is true if all the keys are numeric + */ + private $numericalKey = null; + + /** The minimum key of the serie + */ + private $minKey = null; + + /** The maximum key of the serie + */ + private $maxKey = null; + + /** The numericalValue is true if all the values are numeric + */ + private $numericalValue = null; + + /** The minimum value of the serie + */ + private $minValue = null; + + /** The maximum value of the serie + */ + private $maxValue = null; + + /** The style object for the serie + */ + private $style; + /** If set, hide this serie and do not display it + */ + private $hide; + + /** The axis used to draw the serie. + * If false for main Y axis + * If true for secondary Y axis + */ + private $axisYsecondary = false; + + /** When creating the serie, save the name + * @param string $name The name of the serie + */ + public function __construct($name) + { + if (! is_string($name)) { + throw new \Exception( + dgettext( + "domframework", + "Can't create a serie if the name is not a string" + ), + 406 + ); + } + $this->name = $name; + } + + /** Set the data for the serie + If the parameter is not provided, return the actual $data value + * @param array|null $data The data to store in the serie + */ + public function data($data = null) + { + if ($data === null) { + return $this->data; + } + if (! is_array($data)) { + throw new \Exception( + dgettext( + "domframework", + "Can't create a serie data if the value is not an array" + ), + 406 + ); + } + $this->data = $data; + $this->minmax(); + return $this; + } + + /** Get the minimum and maximum values and keys + */ + private function minmax() + { + if ($this->data === null) { + return null; + } + foreach ($this->data as $key => $value) { + if ($this->minValue === null && is_numeric($value)) { + $this->minValue = $value; + } + if ($this->minKey === null && is_numeric($key)) { + $this->minKey = $key; + } + if (is_numeric($value)) { + $this->minValue = min($this->minValue, $value); + $this->maxValue = max($this->maxValue, $value); + } + if (is_numeric($key)) { + $this->minKey = min($this->minKey, $key); + $this->maxKey = max($this->maxKey, $key); + } + if (! is_numeric($key) && $this->numericalKey === null) { + $this->numericalKey = false; + } + if ( + ! is_numeric($value) && $value !== null && + $this->numericalValue === null + ) { + $this->numericalValue = false; + } + } + if ($this->numericalKey === null) { + $this->numericalKey = true; + } + if ($this->numericalValue === null) { + $this->numericalValue = true; + } + } + + /** Set/get the numeric value of the serie + * If the parameter is not provided, return the actual state + * @param boolean|null $numericalValue The state of the numeric value + */ + public function numericalValue($numericalValue = null) + { + if ($numericalValue === null) { + return $this->numericalValue; + } + if (! is_bool($numericalValue)) { + throw new \Exception(dgettext( + "domframework", + "Invalid numericalValue provided to serie" + ), 406); + } + $this->numericalValue = $numericalValue; + return $this; + } + + /** The minimum value of the serie + */ + public function minValue() + { + if ($this->minValue === null && $this->data !== null) { + $this->minmax(); + } + return $this->minValue; + } + + /** The maximum value of the serie + */ + public function maxValue() + { + if ($this->maxValue === null && $this->data !== null) { + $this->minmax(); + } + return $this->maxValue; + } + + /** Set/get the numeric key of the serie + * If the parameter is not provided, return the actual state + * @param boolean|null $numericalKey The state of the numeric key + */ + public function numericalKey($numericalKey = null) + { + if ($numericalKey === null) { + return $this->numericalKey; + } + if (! is_bool($numericalKey)) { + throw new \Exception(dgettext( + "domframework", + "Invalid numericalKey provided to serie" + ), 406); + } + $this->numericalKey = $numericalKey; + return $this; + } + + /** The minimum key of the serie + */ + public function minKey() + { + if ($this->minKey === null && $this->data !== null) { + $this->minmax(); + } + return $this->minKey; + } + + /** The maximum key of the serie + */ + public function maxKey() + { + if ($this->maxKey === null && $this->data !== null) { + $this->minmax(); + } + return $this->maxKey; + } + + /** The number of elements in the serie + */ + public function count() + { + return count($this->data); + } + + /** Set/Get the graph style for the serie + * If the parameter is not provided, return the actual state + * @param string|object|null $style The graph style + */ + public function style($style = null) + { + if ($style === null) { + return $this->style; + } + if (is_object($style)) { + $this->style = clone $style; + return $this->style; + } + if ( + ! is_string($style) || + ! in_array($style, array("line", "points", "linePoints")) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid style provided to serie" + ), 406); + } + $styleClass = "GraphStyle" . $style; + if ($this->style === null) { + $this->style = new $styleClass(); + } + return $this->style; + } + /** Set/Get the hidden state of the serie + * If the parameter is not provided, return the actual state + * @param boolean|null $hide The hidden state + */ + public function hide($hide = null) + { + if ($hide === null) { + return $this->hide; + } + if (! is_bool($hide)) { + throw new \Exception(dgettext( + "domframework", + "Invalid hide mode provided to serie" + ), 406); + } + $this->hide = $hide; + return $this; + } + + /** The serie is based on the secondary Y axis + * Set the value if the parameter is provided, get the value if the parameter + * is not set + * @param boolean|null $axisYsecondary The Serie on secondary Y axis + */ + public function axisYsecondary($axisYsecondary = null) + { + if ($axisYsecondary === null) { + return $this->axisYsecondary; + } + if (! is_bool($axisYsecondary)) { + throw new \Exception( + dgettext( + "domframework", + "Invalid axisYsecondary provided to graph axisYsecondary" + ), + 406 + ); + } + $this->axisYsecondary = $axisYsecondary; + return $this; + } + + /** Draw the serie with the defined style class + * @param resource $gd The resource to modify + * @param array $free The free space coordinates on the graphic + * @param object $axisX The axis X used on the graph + * @param object $axisY The axis Y used on the graph + */ + public function draw($gd, $free, $axisX, $axisY) + { + if ($this->hide === true) { + return; + } + $this->style->draw($gd, $free, $this->data, $axisX, $axisY); + } +} diff --git a/src/GraphSeries.php b/src/GraphSeries.php new file mode 100644 index 0000000..ece29f6 --- /dev/null +++ b/src/GraphSeries.php @@ -0,0 +1,73 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The series objects */ +class GraphSeries +{ + /** The series stored */ + private $series = array(); + + /** Return the serie object choosed. If doesn't exists, it is created before + * be returned + * @param string $name The name of the serie to create + */ + public function serie($name) + { + if (is_integer($name)) { + $name = dgettext("domframework", "Serie") . " $name"; + } + if (! is_string($name)) { + throw new \Exception( + dgettext( + "domframework", + "Can't get a serie if the name is not a string" + ), + 406 + ); + } + if (! array_key_exists($name, $this->series)) { + $this->series[$name] = new GraphSerie($name); + } + return $this->series[$name]; + } + + /** Get the list of the defined series + */ + public function getList() + { + $series = []; + foreach ($this->series as $name => $serie) { + if ($serie->hide() === true) { + continue; + } + $series[] = $name; + } + return $series; + } + + /** Remove an existing serie + * @param string $name The name of the serie to remove + */ + public function remove($name) + { + if (! is_string($name)) { + throw new \Exception( + dgettext( + "domframework", + "Can't remove a serie if the name is not a string" + ), + 406 + ); + } + if (array_key_exists($name, $this->series)) { + unset($this->series[$name]); + } + } +} diff --git a/src/GraphStyleLine.php b/src/GraphStyleLine.php new file mode 100644 index 0000000..9783df4 --- /dev/null +++ b/src/GraphStyleLine.php @@ -0,0 +1,27 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The graphStyleLine : draw a graph with line */ +class GraphStyleLine extends GraphStyleLinePoints +{ + /** The point color background. To hide the points, choose "transparent" + */ + protected $pointBgcolor = "transparent"; + + /** The point color border + */ + protected $pointColor = "transparent"; + + /** Return the name of the style */ + public function name() + { + return "line"; + } +} diff --git a/src/GraphStyleLinePoints.php b/src/GraphStyleLinePoints.php new file mode 100644 index 0000000..a263433 --- /dev/null +++ b/src/GraphStyleLinePoints.php @@ -0,0 +1,393 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The graphStyleLine : draw a graph with lines */ +class GraphStyleLinePoints +{ + /** The line color. To hide the lines, choose "transparent" + */ + protected $lineColor; + + /** The point color background. To hide the points, choose "transparent" + */ + protected $pointBgcolor; + + /** The point color border + */ + protected $pointColor; + + /** The point shape (square, circle, triangle, lozenge) + */ + protected $pointShape; + + /** The point width in pixel + */ + protected $pointWidth; + + /** The number of the colors/shapes to use + */ + protected $number; + + /** The allowed shapes */ + protected $allowedShapes = array("square", "circle", "triangle", "lozenge"); + + /** The palette to use */ + protected $palette; + + /** Return the name of the style */ + public function name() + { + return "lineAndPoints"; + } + + /** Set the line color if the parameter is provided. + * Get the line color if the parameter is not provided + * @param string|null $lineColor The line color + */ + public function lineColor($lineColor = null) + { + if ($lineColor === null) { + return $this->lineColor; + } + if ( + ! is_string($lineColor) || + ($lineColor !== "transparent" && + ! in_array($lineColor, Color::colorList())) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid lineColor provided to line style" + ), 406); + } + $this->lineColor = $lineColor; + return $this; + } + + /** Set the palette to use if the parameter is provided + * Get the palette if the parameter is not provided + * @param string|null $palette The palette to use + */ + public function palette($palette = null) + { + if ($palette === null) { + return $this->palette; + } + if (! is_string($palette)) { + throw new \Exception(dgettext( + "domframework", + "Invalid palette provided to line style" + ), 406); + } + $this->palette = $palette; + return $this; + } + + /** Set the point background color if the parameter is provided. + * Get the point background color if the parameter is not provided + * @param string|null $pointBgcolor The point background color + */ + public function pointBgcolor($pointBgcolor = null) + { + if ($pointBgcolor === null) { + return $this->pointBgcolor; + } + if ( + ! is_string($pointBgcolor) || + ($pointBgcolor !== "transparent" && + ! in_array($pointBgcolor, Color::colorList())) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid pointBgcolor provided to line style" + ), 406); + } + $this->pointBgcolor = $pointBgcolor; + return $this; + } + + /** Set the point border color if the parameter is provided. + * Get the point border color if the parameter is not provided + * @param string|null $pointColor The point border color + */ + public function pointColor($pointColor = null) + { + if ($pointColor === null) { + return $this->pointColor; + } + if ( + ! is_string($pointColor) || + ($pointColor !== "transparent" && + ! in_array($pointColor, Color::colorList())) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid pointColor provided to line style" + ), 406); + } + $this->pointColor = $pointColor; + return $this; + } + + /** Set the point shape if the parameter is provided. + * Get the point shape if the parameter is not provided + * @param string|null $pointShape The point shape + */ + public function pointShape($pointShape = null) + { + if ($pointShape === null) { + return $this->pointShape; + } + if ( + ! is_string($pointShape) || + ! in_array($pointShape, $this->allowedShapes) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid pointShape provided to line style" + ), 406); + } + $this->pointShape = $pointShape; + return $this; + } + + /** Set the point width if the parameter is provided. + * Get the point width if the parameter is not provided + * @param string|null $pointWidth The point width + */ + public function pointWidth($pointWidth = null) + { + if ($pointWidth === null) { + return $this->pointWidth; + } + if (! is_integer($pointWidth) || $pointWidth < 3 || $pointWidth > 20) { + throw new \Exception(dgettext( + "domframework", + "Invalid pointWidth provided to line style" + ), 406); + } + $this->pointWidth = $pointWidth; + return $this; + } + + /** Select the colors, shapes by the serie number. + * Do not change any property if the property is already defined + * If the parameter is not provided, return the value + * @param integer|null $number The serie number + */ + public function number($number = null) + { + if ($number === null) { + return $this->number; + } + if (! is_int($number) || $number < 0) { + throw new \Exception(dgettext( + "domframework", + "Invalid number provided to line style number" + ), 406); + } + $this->number = $number; + if ($this->pointShape === null) { + $this->pointShape = $this->allowedShapes[ + ($number % count($this->allowedShapes))]; + } + $palette = graphPalette::getPalette($this->palette); + if ($this->pointBgcolor === null) { + $this->pointBgcolor = $palette[($number % count($palette))]["bgcolor"]; + } + if ($this->pointColor === null) { + $this->pointColor = $palette[($number % count($palette))]["color"]; + } + if ($this->lineColor === null) { + $this->lineColor = $palette[($number % count($palette))]["color"]; + } + } + + /** Draw in the $gd resource, in the $free array, the data with the parameter + * of the style + * @param resource $gd The resource to modify + * @param array $free The free space coordinates on the graphic + * @param array $data The data to graph + * @param object $axisX The X axis used to graph + * @param object $axisY The Y axis used to graph + */ + public function draw($gd, $free, $data, $axisX, $axisY) + { + if ($this->lineColor !== "transparent") { + $lineColor = Color::allocateFromText($gd, $this->lineColor); + } + $lastX = null; + $lastY = null; + foreach ($data as $key => $value) { + $posX = $axisX->position($key); + $posY = $axisY->position($value); + if ($posX === null || $posY === null) { + // NULL position : skip the point + $lastX = null; + $lastY = null; + continue; + } + + // Draw the lines between points (except if the last point was null, or + // if the line color is "transparent") + if ( + $lastX !== null && $lastY !== null && + $this->lineColor !== "transparent" + ) { + imageline($gd, $lastX, $lastY, $posX, $posY, $lineColor); + // Redraw the old point which was scratch by the new created line + $this->drawPoint($gd, $lastX, $lastY); + } + if ($lastX === null) { + $lastX = $posX; + } + if ($lastY === null) { + $lastY = $posY; + } + + // Draw the point. Will overwrite the lines + if ($this->pointWidth === null) { + $this->pointWidth = 6; + } + $this->drawPoint($gd, $posX, $posY); + $lastX = $posX; + $lastY = $posY; + } + } + + /** Draw a point defined in the property of the class + * @param resource $gd The resource to modify + * @param integer $posX The X position to draw the point + * @param integer $posY The Y position to draw the point + */ + private function drawPoint($gd, $posX, $posY) + { + if ($this->pointColor !== "transparent") { + $pointColor = Color::allocateFromText($gd, $this->pointColor); + } + if ($this->pointBgcolor !== "transparent") { + $pointBgcolor = Color::allocateFromText($gd, $this->pointBgcolor); + } + $half = intval($this->pointWidth / 2); + switch ($this->pointShape) { + case "circle": + if ($this->pointBgcolor !== "transparent") { + imagefilledellipse( + $gd, + $posX, + $posY, + $this->pointWidth, + $this->pointWidth, + $pointBgcolor + 2 + ); + } + if ($this->pointColor !== "transparent") { + imageellipse( + $gd, + $posX, + $posY, + $this->pointWidth + 2, + $this->pointWidth + 2, + $pointColor + ); + } + break; + case "lozenge": + if ($this->pointBgcolor !== "transparent") { + imagefilledpolygon( + $gd, + array($posX - $half - 2, $posY, + $posX, $posY - $half - 2, + $posX + $half + 2, $posY, + $posX, $posY + $half + 2), + 4, + $pointBgcolor + ); + } + if ($this->pointColor !== "transparent") { + imagepolygon( + $gd, + array($posX - $half - 2, $posY, + $posX, $posY - $half - 2, + $posX + $half + 2, $posY, + $posX, $posY + $half + 2), + 4, + $pointColor + ); + } + break; + case "square": + if ($this->pointBgcolor !== "transparent") { + imagefilledrectangle( + $gd, + $posX - $half - 1, + $posY - $half - 1, + $posX + $half + 1, + $posY + $half + 1, + $pointBgcolor + ); + } + if ($this->pointColor !== "transparent") { + imagerectangle( + $gd, + $posX - $half - 1, + $posY - $half - 1, + $posX + $half + 1, + $posY + $half + 1, + $pointColor + ); + } + break; + case "triangle": + if ($this->pointBgcolor !== "transparent") { + imagefilledpolygon( + $gd, + array($posX - $half - 2, $posY + $half + 2, + $posX, $posY - $half - 2, + $posX + $half + 2, $posY + $half + 2), + 3, + $pointBgcolor + ); + } + if ($this->pointColor !== "transparent") { + imagepolygon( + $gd, + array($posX - $half - 2, $posY + $half + 2, + $posX, $posY - $half - 2, + $posX + $half + 2, $posY + $half + 2), + 3, + $pointColor + ); + } + break; + default: + throw new \Exception(dgettext( + "domframework", + "Unknown pointShape for serie" + ), 406); + } + } + + /** Draw a sample of the style for the legend + * @param resource $gd The resource to modify + * @param integer $x The central position of the sample in x + * @param integer $y The central position of the sample in y + */ + public function sample($gd, $x, $y) + { + if ($this->lineColor !== "transparent") { + $lineColor = Color::allocateFromText($gd, $this->lineColor); + imageline($gd, $x - 10, $y, $x + 10, $y, $lineColor); + } + if ($this->pointWidth === null) { + $this->pointWidth = 6; + } + $this->drawPoint($gd, $x, $y); + } +} diff --git a/src/GraphStylePoints.php b/src/GraphStylePoints.php new file mode 100644 index 0000000..c9c3e5f --- /dev/null +++ b/src/GraphStylePoints.php @@ -0,0 +1,23 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The graphStylePoints : draw a graph with points */ +class GraphStylePoints extends GraphStyleLinePoints +{ + /** The line color : transparent + */ + protected $lineColor = "transparent"; + + /** Return the name of the style */ + public function name() + { + return "points"; + } +} diff --git a/src/GraphTitle.php b/src/GraphTitle.php new file mode 100644 index 0000000..09c5922 --- /dev/null +++ b/src/GraphTitle.php @@ -0,0 +1,167 @@ + + * @license BSD + */ + +namespace Domframework; + +/** The graphTitle object */ +class GraphTitle +{ + /** The title text + */ + private $text = null; + + /** The TTF fontfile to use + */ + private $fontfile = null; + + /** The font size to use + */ + private $fontsize = 14; + + /** The title color + */ + private $color = "black"; + + /** The padding arround the title (in px) + */ + private $padding = 10; + + /** Set the text of the title if the parameter is provided. + * Get the text of the title if the parameter is not provided + * @param string|null $text The text of the title + */ + public function text($text = null) + { + if ($text === null) { + return $this->text; + } + if (! is_string($text) || strlen($text) < 0 || strlen($text) > 50) { + throw new \Exception(dgettext( + "domframework", + "Invalid text provided to graph title" + ), 406); + } + $this->text = $text; + return $this; + } + + /** Set the fontfile of the title if the parameter is provided. + * Get the fontfile of the title if the parameter is not provided + * @param string|null $fontfile The fontfile of the title + */ + public function fontfile($fontfile = null) + { + if ($fontfile === null) { + return $this->fontfile; + } + if ( + ! is_string($fontfile) || strlen($fontfile) < 0 || + ! file_exists($fontfile) || ! is_readable($fontfile) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid fontfile provided to graph title" + ), 406); + } + $this->fontfile = $fontfile; + return $this; + } + + /** Set the font size of the title if the parameter is provided. + * Get the font size of the title if the parameter is not provided + * @param integer|null $fontsize The font size of the title + */ + public function fontsize($fontsize = null) + { + if ($fontsize === null) { + return $this->fontsize; + } + if (! is_integer($fontsize) || $fontsize < 2 || $fontsize > 100) { + throw new \Exception(dgettext( + "domframework", + "Invalid fontsize provided to graph title" + ), 406); + } + $this->fontsize = $fontsize; + return $this; + } + + /** Set the color of the title if the parameter is provided. + * Get the color of the title if the parameter is not provided + * @param string|null $color The color of the title + */ + public function color($color = null) + { + if ($color === null) { + return $this->color; + } + if ( + ! is_string($color) || + ! in_array($color, Color::colorList()) + ) { + throw new \Exception(dgettext( + "domframework", + "Invalid color provided to graph title" + ), 406); + } + $this->color = $color; + return $this; + } + + /** Set the padding of the title if the parameter is provided. + * Get the padding of the title if the parameter is not provided + * @param integer|null $padding The padding of the title + */ + public function padding($padding = null) + { + if ($padding === null) { + return $this->padding; + } + if (! is_integer($padding) || $padding < 0 || $padding > 200) { + throw new \Exception(dgettext( + "domframework", + "Invalid padding provided to graph title" + ), 406); + } + $this->padding = $padding; + return $this; + } + + /** Draw the title in the $gd resource provided + * @param resource $gd The resource to modify + * @param array $free The free space coordinates on the graphic + * @return array the new free coordinates array + */ + public function draw($gd, $free) + { + if ($this->text === null) { + return $free; + } + // Look for the bounding box around the text. The bounding is not write on + // the image and return the coordinates for the text box + $bbox = imagettfbbox($this->fontsize, 0, $this->fontfile, $this->text); + // Calculate the position of the text to be centered on the graph + // The padding is only on vertical : the x is centered + $x = floor(($free[2] - $free[0] - abs($bbox[4] - $bbox[0])) / 2); + $y = ceil($free[1] + abs($bbox[5] - $bbox[1])) + $this->padding; + $x += $free[0]; + $y += $free[1]; + imagettftext( + $gd, + $this->fontsize, + 0, + $x, + $y, + Color::allocateFromText($gd, $this->color), + $this->fontfile, + $this->text + ); + return array(intval($free[0]), intval($free[1] + $y + $this->padding), + intval($free[2]), intval($free[3])); + } +} diff --git a/src/HttpConnection.php b/src/HttpConnection.php new file mode 100644 index 0000000..73260ca --- /dev/null +++ b/src/HttpConnection.php @@ -0,0 +1,64 @@ + + * @license BSD + */ + +namespace Domframework; + +/** + * Manage the HTTP Connections. + * Used by HttpServer + */ +class HttpConnection +{ + private $tcpserver; + private $clientAddress; + private $clientPort; + private $localAddress; + private $localPort; + private $logger; + + public function __construct($tcpserver) + { + $this->tcpserver = $tcpserver; + list($clientAddress, $clientPort, $localAddress, $localPort) = + $tcpserver->getInfo(); + // If the address is in IPv4 in IPv6 (syntax : "::ffff:127.0.0.1"), + // update it to IPv4 only + if (substr($clientAddress, 0, 7) === "::ffff:") { + $clientAddress = substr($clientAddress, 7); + } + $this->clientAddress = $clientAddress; + $this->clientPort = $clientPort; + $this->localAddress = $localAddress; + $this->localPort = $localPort; + echo "OK : $localAddress:$localPort"; + } + + public function __destruct() + { + } + + public function getClientAddress() + { + return $this->clientAddress; + } + + public function getClientPort() + { + return $this->clientPort; + } + + public function getLocalAddress() + { + return $this->localAddress; + } + + public function getLocalPort() + { + return $this->localPort; + } +} diff --git a/src/Httpserver.php b/src/Httpserver.php new file mode 100644 index 0000000..4c46b72 --- /dev/null +++ b/src/Httpserver.php @@ -0,0 +1,144 @@ + + * @license BSD + */ + +namespace Domframework; + +/** + * Create a HTTP server easily + * The server can listen on multiple IP addresses and multiple ports. + * + * Need HttpConnection + */ +class Httpserver +{ + private $tcpserver; + private $listeners = array(); + private $runningUser; + private $runningGroup; + private $logger; + private $maxConnection = 500; + private $timeout = 30; + + /** Add a new listener. Must all be set before entering in loop + * @param string $ip The Listening IP Address + * @param integer $port the Listening port + * @param callable $callable The method called when a new request enter + */ + public function addListener($ip, $port, $callable) + { + $ipaddresses = new Ipaddresses(); + if (! $ipaddresses->validIPAddress($ip)) { + throw new \Exception( + "Httpserver : invalid IP address provided to listen", + 500 + ); + } + if ( + ! is_numeric($port) || ! ctype_digit($port) || $port < 0 || + $port > 65535 + ) { + throw new \Exception( + "Httpserver : invalid port provided to listen " . + "(must be between 0 and 65535)", + 500 + ); + } + if (! is_callable($callable)) { + throw new \Exception( + "Httpserver : invalid callable function provided to listen " . + "(must be callable)", + 500 + ); + } + foreach ($this->listeners as $listener) { + if ($listener["ip"] === $ip && $listener["port"] === $port) { + throw new \Exception("Httpserver : can not add multiple listeners on " . + "the same couple IP/Port", 500); + } + } + $this->listeners[] = ["ip" => $ip, + "port" => $port, + "callable" => $callable]; + return $this; + } + + private function setLogger($logger) + { + $this->logger = $logger; + return $this; + } + + public function getMaxConnection() + { + return $this->maxConnection; + } + + public function setMaxConnection($maxConnection) + { + $this->maxConnection = $maxConnection; + return $this; + } + + public function getTimeout() + { + return $this->timeout; + } + + public function setTimeout($timeout) + { + $this->timeout = $timeout; + return $this; + } + public function getRunningUser() + { + return $this->runningUser; + } + + public function setRunningUser($user) + { + $this->runningUser = $user; + return $this; + } + + public function getRunningGroup() + { + return $this->runningGroup; + } + + public function setRunningGroup($group) + { + $this->runningGroup = $group; + return $this; + } + + final public function loop() + { + $tcpserver = new Tcpserver(); + if (empty($this->listeners)) { + throw new \Exception("Httpserver : No listener defined"); + } + foreach ($this->listeners as $listener) { + $tcpserver->init($listener["ip"], $listener["port"], [$this, "connect"]); + } + $tcpserver->maxChild($this->maxConnection); + $tcpserver->loop(); + } + + final public function connect($tcpserver) + { + if ($this->runningUser) { + $user = posix_getpwnam($this->runningUser); + posix_setuid($user['uid']); + } + if ($this->runningGroup) { + $group = posix_getgrnam($this->runningGroup); + posix_setgid($group['gid']); + } + $httpConnection = new HttpConnection($tcpserver); + } +} diff --git a/src/RssItem.php b/src/RssItem.php new file mode 100644 index 0000000..28560a3 --- /dev/null +++ b/src/RssItem.php @@ -0,0 +1,196 @@ + + * @license BSD + */ + +namespace Domframework; + +/** + * This class manage one RSS Item + * + * Called by Rss + */ +class RssItem +{ + /** The title of the item + */ + private $title; + + /** The URL of the item + */ + private $link; + + /** The item synopsis + */ + private $description; + + /** Email address of the author of the item + */ + private $author; + + /** URL of a page for comments relating to the item + */ + private $comments; + + /** A string that uniquely identifies the item + */ + private $guid; + + /** Indicates when the item was published + */ + private $pubDate; + + /** Get/Set the value + * @param string|null $title The title to get/set + */ + public function title($title = null) + { + if ($title === null) { + return $this->title; + } + if (! is_string($title)) { + throw new \Exception("Title provided to RSS Item is not a string", 500); + } + if ($title === "") { + $title = null; + } + $this->title = $title; + return $this; + } + + /** Get/Set the value + * @param string|null $link The link to get/set + */ + public function link($link = null) + { + if ($link === null) { + return $this->link; + } + if (! is_string($link)) { + throw new \Exception("Link provided to RSS Item is not a string", 500); + } + $verify = new Verify(); + if (! $verify->is_URL($link)) { + throw new \Exception("Link provided to RSS Item is not an URL", 500); + } + if ($link === "") { + $link = null; + } + $this->link = $link; + return $this; + } + + /** Get/Set the value + * @param string|null $description The description to get/set + */ + public function description($description = null) + { + if ($description === null) { + return $this->description; + } + if (! is_string($description)) { + throw new \Exception( + "Description provided to RSS Item is not a string", + 500 + ); + } + if ($description === "") { + $description = null; + } + $this->description = $description; + return $this; + } + + /** Get/Set the value + * @param string|null $author The author to get/set + */ + public function author($author = null) + { + if ($author === null) { + return $this->author; + } + if (! is_string($author)) { + throw new \Exception("Author provided to RSS Item is not a string", 500); + } + if ($author === "") { + $author = null; + } + $this->author = $author; + return $this; + } + + /** Get/Set the value + * @param string|null $comments The comments to get/set + */ + public function comments($comments = null) + { + if ($comments === null) { + return $this->comments; + } + if (! is_string($comments)) { + throw new \Exception( + "Comments provided to RSS Item is not a string", + 500 + ); + } + if ($comments === "") { + $comments = null; + } + $this->comments = $comments; + return $this; + } + + /** Get/Set the value + * @param string|null $guid The guid to get/set + */ + public function guid($guid = null) + { + if ($guid === null) { + return $this->guid; + } + if (! is_string($guid)) { + throw new \Exception("GUID provided to RSS Item is not a string", 500); + } + if ($guid === "") { + $guid = null; + } + $this->guid = $guid; + return $this; + } + + /** Get/Set the value + * @param string|null $pubDate The pubDate to get/set + */ + public function pubDate($pubDate = null) + { + if ($pubDate === null) { + return $this->pubDate; + } + if (! is_string($pubDate)) { + throw new \Exception( + "pubDate provided to RSS Item is not a string", + 500 + ); + } + if (! Verify::staticIs_datetimeSQL($pubDate)) { + throw new \Exception( + "pubDate provided to RSS Item is not a valid date", + 500 + ); + } + if ($pubDate === "") { + $pubDate = null; + } else { + $pubDate = Convert::convertDate( + $pubDate, + "Y-m-d H:i:s", + \DateTime::RFC2822 + ); + } + $this->pubDate = $pubDate; + return $this; + } +} diff --git a/src/StateMachineState.php b/src/StateMachineState.php new file mode 100644 index 0000000..08eeb1b --- /dev/null +++ b/src/StateMachineState.php @@ -0,0 +1,24 @@ + + * @license BSD + */ + +namespace Domframework; + +class StateMachineState +{ + private $methodName; + + public function __construct(callable $methodName) + { + $this->methodName = $methodName; + } + + public function run() + { + return call_user_func($this->methodName); + } +} diff --git a/src/StateMachineTransition.php b/src/StateMachineTransition.php new file mode 100644 index 0000000..a324614 --- /dev/null +++ b/src/StateMachineTransition.php @@ -0,0 +1,43 @@ + + * @license BSD + */ + +namespace Domframework; + +class StateMachineTransition +{ + private $fromStateName; + private $toStateName; + private $methodName; + + public function __construct(string $fromStateName, string $toStateName, callable $methodName) + { + $this->fromStateName = $fromStateName; + $this->toStateName = $toStateName; + $this->methodName = $methodName; + } + + public function getFromStateName(): string + { + return $this->fromStateName; + } + + public function getToStateName(): string + { + return $this->toStateName; + } + + public function getMethodName(): callable + { + return $this->methodName; + } + + public function run() + { + return call_user_func($this->methodName); + } +}