sddns/include/output.php

457 lines
12 KiB
PHP
Executable file

<?php
/**
* Output classes
*
* @copyright 2013 Steffen Vogel
* @license http://www.gnu.org/licenses/gpl.txt GNU Public License
* @author Steffen Vogel <post@steffenvogel.de>
* @link http://www.steffenvogel.de
*/
/*
* This file is part of sddns
*
* sddns is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* sddns is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sddns. If not, see <http://www.gnu.org/licenses/>.
*/
class JsonOutput extends Output {
public function __construct($debug) {
parent::__construct('application/json', 'UTF-8', $debug);
}
protected function getOutput() {
$json = array();
foreach ($this->getMessages() as $index => $message) {
array_push($json, $message);
}
// JSONP
if (!empty($_REQUEST['callback'])) {
return $_REQUEST['callback'] . '(' . json_encode($json) . ');';
}
else {
return json_encode($json);
}
}
}
class XmlOutput extends Output {
public function __construct($debug) {
parent::__construct('text/xml', 'UTF-8', $debug);
}
protected function getOutput() {
$doc = new DomDocument('1.0', $this->encoding);
$doc->formatOutput = true;
$xmlSddns = $doc->createElement('sddns');
$doc->appendChild($xmlSddns);
foreach ($this->getMessages() as $index => $message) {
$xmlMessage = $doc->createElement('message');
$xmlMessage->setAttribute('type', $message['type']);
if ($message['type'] == 'debug') {
$xmlMessage->setAttribute('level', $message['level']);
}
$xmlMessage->appendChild($doc->createElement('description', $message['description']));
foreach ($message['data'] as $object) {
if (method_exists($object, 'toXml')) {
$xmlMessage->appendChild($object->toXml($doc));
}
else {
$xmlMessage->appendChild($doc->createElement(gettype($object), $object));
}
}
$xmlSddns->appendChild($xmlMessage);
}
return $doc->saveXML();
}
}
// needs JPGraph >= 3.0.7!
class GraphOutput extends Output {
private $graph;
public function __construct($debug) {
parent::__construct('text/html', 'UTF-8', $debug);
global $site;
require_once 'jpgraph/jpgraph.php';
}
public function getGraph($width, $height, $type = 'line') {
switch ($type) {
case 'line':
$this->graph = new Graph($width, $height);
break;
case 'pie':
$this->graph = new PieGraph($width, $height);
break;
}
return $this->graph;
}
public function showGraph() {
if (isset($this->graph))
$this->graph->Stroke();
}
protected function getOutput() { // TODO
if (count($this->getMessages()) > 0) {
echo '<pre>';
print_r($this->getMessages());
echo '</pre>';
}
}
}
class GifOutput extends Output {
public function __construct() {
parent::__construct('image/gif');
}
protected function getOutput() {
$im = imagecreate(1, 1);
$red = imagecolorallocate($im, 255, 0, 0);
$green = imagecolorallocate($im, 0, 255, 0);
imagefill($im, 0, 0, (count($this->getMessages(false, array('error', 'exception'))) > 0) ? $red : $green);
imagegif($im);
imagedestroy($im);
}
}
class PlainLineOutput extends Output {
public function __construct($debug, $fields = array('index', 'time', 'type', 'description', 'data'), $delimiter = "\t", $lineDelimiter = "\n", $escape = true) {
parent::__construct('text/plain', 'UTF-8', $debug);
$this->fields = $fields;
$this->delimiter = $delimiter;
$this->lineDelimiter = $lineDelimiter;
}
protected function getOutput() {
global $argv;
$fd = fopen('php://memory', 'w+');
foreach($this->getMessages() as $index => $message) {
if (isset($argv)) {
$fd = (in_array($message['type'], array('error', 'exception', 'warning'))) ? STDERR : STDOUT;
}
foreach ($this->fields as $fieldIndex => $field) {
switch ($field) {
case 'type':
fwrite($fd, '[' . $message['type'] . ']');
break;
case 'index':
fwrite($fd, $index);
break;
case 'time':
fwrite($fd, date('Y-m-d H:i:s', $message['time']));
break;
case 'description':
fwrite($fd, $message['description']);
break;
case 'data':
fwrite($fd, implode(', ', $message['data']));
break;
default:
fwrite($fd, $message[$field]);
}
fwrite($fd, ($fieldIndex == count($this->fields) - 1) ? $this->lineDelimiter : $this->delimiter);
}
}
if (isset($argv)) {
exit( (count($this->getMessages(false, array('error', 'exception')))) ? 1 : 0 );
}
else {
rewind($fd);
$str = stream_get_contents($fd);
fclose($fd);
return $str;
}
}
}
class HtmlOutput extends Output {
public function __construct($debug) {
parent::__construct('text/html', 'UTF-8', $debug);
ob_start();
}
protected function getOutput() {
global $site;
$columnCount = 0;
$messages = $this->getMessages();
$html = ob_get_clean();
foreach ($messages as $message) {
if (count($message['data']) > $columnCount)
$columnCount = count($message['data']);
}
$str = '<?xml version="1.0" ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>/dev/nulll - Simple Dynamic Domain Name Service</title>
<script src="' . $site['path']['web'] . '/include/script.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="' . $site['path']['web'] . '/include/style.css" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="icon" href="/favicon.png" type="image/png" />
<link rel="search" type="application/opensearchdescription+xml" title="Tiny DNS &amp; URL" href="' . $site['path']['web'] . '/opensearch.xml" />
</head>
<body>
<div id="wrapper">';
$str .= '<div id="content">' . $html . '</div>';
if (count($messages)) {
$str .= '<table id="messages" style="' . ((strlen($html)) ? 'bottom' : 'top') . ': 0">
<tr><th class="type">type</th><th class="index">index</th><th class="time">time</th><th>description</th><th colspan="100">data</th></tr>';
foreach ($messages as $index => $message) {
$str .= '<tr class="' . $message['type'] . '">
<td><img alt="' . $message['type'] . '" title="' . $message['type'] . '" src="' . $site['path']['web'] . '/images/' . $message['type'] . '.png" title="' . @$message['level'] . '" /></td>
<td>#' . $index . '</td>
<td>' . date('Y-m-d H:i', $message['time']) . '</td>
<td>' . $message['description'] . '</td>';
for($i = 0; $i < $columnCount; $i++) {
$str .= '<td>';
if (isset($message['data'][$i])) {
$data = $message['data'][$i];
if (is_object($data) && method_exists($data, 'toHtml')) {
$str .= $data->toHtml();
}
else {
$str .= $data;
}
}
$str .= '</td>';
}
$str .= '</tr>';
}
$str .= '</table>';
}
$str .= '</div></body></html>';
return $str;
}
}
abstract class Output {
protected $messages = array();
public $debug = 0;
public $format;
protected $contentType;
protected $encoding;
function __construct($contentType = 'text/plain', $encoding = 'UTF-8', $debug = 0) {
$this->contentType = $contentType;
$this->encoding = $encoding;
$this->debug = $debug;
header('Content-type: ' . $this->contentType . (($this->encoding != null) ? '; charset=' . $this->encoding : ''));
}
function add($description, $type = 'notice') {
$message['time'] = time();
$message['description'] = $description;
$message['type'] = $type;
$message['data'] = array();
$argv = func_get_args();
$argc = count($argv);
if ($type == 'debug') {
$message['level'] = $argv[2];
}
for ($i = ($type == 'debug') ? 3 : 2; $i < $argc; $i++) {
if (empty($argv[$i])) continue;
if (!is_array($argv[$i])) {
$message['data'][] = $argv[$i];
}
else {
$message['data'] = array_merge($message['data'], array_values($argv[$i]));
}
}
array_push($this->messages, $message);
}
protected function getMessages($exclude = true, $args = null) {
$types = array('notice', 'success', 'error', 'exception', 'warning', 'data', 'debug');
if ($args == null)
$args = array();
$types = ($exclude) ? array_diff($types, $args) : $args;
$messages = array();
foreach ($this->messages as $message) {
if (in_array($message['type'], $types) && ($message['type'] != 'debug' || $message['level'] <= $this->debug)) {
$messages[] = $message;
}
}
return $messages;
}
static function getInstance($format = null, $debug = 0) {
switch (strtolower($format)) {
case 'xml':
return new XmlOutput($debug);
break;
case 'txt':
return new PlainLineOutput($debug, array('type', 'description', 'data'), "\t", "\n");
break;
case 'csv':
return new PlainLineOutput($debug, array('time', 'type', 'description', 'data'), ";", "\n");
break;
case 'png':
return new GraphOutput($debug);
break;
case 'gif':
return new GifOutput();
break;
case 'json':
return new JsonOutput($debug);
case 'html':
case 'php':
default:
return new HtmlOutput($debug);
break;
}
}
static function start($forced = null) {
global $argc;
global $site;
if (isset($forced))
$format = $forced;
elseif (isset($argc))
$format = 'txt';
elseif (empty($_REQUEST['format']) || @$_REQUEST['format'] == 'php')
$format = 'html';
else
$format = $_REQUEST['format'];
$output = self::getInstance($format, $site['debug']);
// errorhandling
set_exception_handler(array($output, 'exception_handler'));
set_error_handler(array($output, 'error_handler'), E_ALL);
// shutdown
register_shutdown_function(array($output, 'send'));
// debugging
$parameters = array();
foreach ($_REQUEST as $parName => $parValue) {
$parameters[] = $parName . ' => ' . $parValue;
}
$output->add('debug level', 'debug', 2, $output->debug);
$output->add('parameters', 'debug', 2, $parameters);
return $output;
}
function exception_handler($exception) {
if (is_subclass_of($exception, 'CustomException')) {
$this->add($exception->getMessage(), 'error', $exception->getData());
}
else {
$this->debug = 7; // increase verbosity in case of an exception
$this->add(get_class($exception), 'exception', (array) $exception);
}
switch (get_class($exception)) {
case 'CustomException': http_response_code(500); break;
case 'UserException': http_response_code(400); break;
case 'ValidationException': http_response_code(400); break;
case 'AuthentificationException': http_response_code(403); break;
default: $code = 500; break;
}
exit(1);
}
function error_handler($errno, $errstr, $errfile, $errline) {
if (($errno & error_reporting()) != 0) {
switch ($errno) {
case E_USER_WARNING:
case E_WARNING:
$type = 'warning';
$str = $type;
break;
case E_USER_NOTICE:
case E_NOTICE:
$type = 'warning';
$str = 'notice';
break;
case E_USER_ERROR:
case E_ERROR:
default:
$type = 'error';
$str = $type;
break;
}
$this->add($str, $type, $errstr . ' in ' . $errfile . ':' . $errline);
}
}
abstract protected function getOutput();
function send() {
echo $this->getOutput();
}
}