forked from 4ducky/4ducky
The PHP sources, templates and old jQuery-based scripts move unchanged into legacy/ for reference. Shared assets (media/, images/, favicon.ico, the default avatar) stay at the repository root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
650 lines
18 KiB
PHP
650 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Generate a simple api
|
|
*
|
|
* @author Daniel Leitner
|
|
* @version 1.2
|
|
* @link mail@danielleitner.de
|
|
*/
|
|
|
|
class Api {
|
|
|
|
/**
|
|
* Mysql connection
|
|
*
|
|
* @var resource
|
|
* @access private
|
|
*/
|
|
private $_db;
|
|
|
|
/**
|
|
* Store namcespaces
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
private $_namespaces = array();
|
|
|
|
/**
|
|
* Working namespace name
|
|
*
|
|
* @var string
|
|
* @access private
|
|
*/
|
|
private $_current_namaspace;
|
|
|
|
/**
|
|
* Namespace which is rendered
|
|
*
|
|
* @var string
|
|
* @access private
|
|
*/
|
|
private $_render_namepsace;
|
|
|
|
/**
|
|
* Url data
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
private $_route_data;
|
|
|
|
/**
|
|
* Class options
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
private $_options = array(
|
|
'output_types' => array(
|
|
'json', 'xml', 'csv', 'yaml'
|
|
)
|
|
);
|
|
|
|
/**
|
|
* Rules to check the route
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
private $_rules = array(
|
|
'alpha_numeric', 'numeric'
|
|
);
|
|
|
|
/**
|
|
* Select result for api
|
|
*
|
|
* @var array
|
|
*/
|
|
private $_result = array();
|
|
|
|
/**
|
|
* Let the magic start ;)
|
|
*
|
|
* @param resource $db
|
|
* @access public
|
|
*/
|
|
public function __construct($db) {
|
|
|
|
$this->_db = $db;
|
|
}
|
|
|
|
/**
|
|
* Register new namespace
|
|
*
|
|
* @param type $namespace
|
|
* @todo Set defaults to array
|
|
* @access public
|
|
* @return Api
|
|
*/
|
|
public function register($namespace) {
|
|
|
|
if( ! isset($this->_namespaces[$namespace]) ) {
|
|
$this->_namespaces[$namespace] = array(
|
|
'key' => false,
|
|
'sort' => false,
|
|
'request' => false,
|
|
'limit' => false,
|
|
'cache' => false,
|
|
'conditions' => false
|
|
);
|
|
|
|
$this->_current_namaspace = $namespace;
|
|
} else {
|
|
$this->_error('Namespace already exists.');
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $message
|
|
* @access private
|
|
* @todo output
|
|
* @return Api
|
|
*/
|
|
private function _error($message) {
|
|
header("Content-Type: application/json");
|
|
|
|
die(json_encode($message));
|
|
}
|
|
|
|
/**
|
|
* Set table name
|
|
*
|
|
* @param string $table
|
|
* @return Api
|
|
*/
|
|
public function setTable($table) {
|
|
|
|
if( is_string($table) ) {
|
|
$this->_namespaces[$this->_current_namaspace]['table'] = $table;
|
|
} else {
|
|
$this->_error('Table name musst be a string.');
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set output type
|
|
*
|
|
* @access public
|
|
* @param array|string $output
|
|
* @return Api
|
|
*/
|
|
public function setOutput($output) {
|
|
|
|
if( is_array($output) ) {
|
|
foreach($output as $o) {
|
|
if( ! in_array($o, $this->_options['output_types']) ) {
|
|
$this->_error('Output as '.$o.' is not supported.');
|
|
}
|
|
}
|
|
|
|
$this->_namespaces[$this->_current_namaspace]['output'] = $output;
|
|
} else {
|
|
|
|
if( ! in_array($output, $this->_options['output_types']) ) {
|
|
$this->_error('Output as '.$output.' is not supported.');
|
|
}
|
|
|
|
$this->_namespaces[$this->_current_namaspace]['output'] = $output;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set if unique key is required (false = default)
|
|
*
|
|
* @access public
|
|
* @param boolean $key
|
|
* @return Api
|
|
*/
|
|
public function setKey($key) {
|
|
|
|
if( is_bool($key) ) {
|
|
$this->_namespaces[$this->_current_namaspace]['key'] = $key;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set request limit per hour. (false = default)
|
|
*
|
|
* @access public
|
|
* @param integer $limit
|
|
* @return Api
|
|
*/
|
|
public function setLimit($limit) {
|
|
|
|
if( is_integer($limit) ) {
|
|
$this->_namespaces[$this->_current_namaspace]['limit'] = $limit;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
// Limit output
|
|
public function setRequest($request) {
|
|
|
|
if( is_integer($request) ) {
|
|
$this->_namespaces[$this->_current_namaspace]['request'] = $request;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
|
|
// Sort output
|
|
public function setSort($sort) {
|
|
|
|
if( is_string($sort) ) {
|
|
$this->_namespaces[$this->_current_namaspace]['sort'] = $sort;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Activate cache
|
|
*
|
|
* @param integer $time
|
|
* @param string $folder
|
|
* @return Api
|
|
*/
|
|
public function setCache($time, $folder) {
|
|
|
|
if( ! is_dir($folder) ) {
|
|
$this->_error('Cache folder musst be a folder.');
|
|
}
|
|
|
|
if( ! is_integer($time) ) {
|
|
$this->_error('Cache time musst be integer.');
|
|
}
|
|
|
|
$this->_namespaces[$this->_current_namaspace]['cache'] = array(
|
|
'folder' => $folder,
|
|
'time' => $time
|
|
);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set columns which should be displayed
|
|
*
|
|
* @param array $columns
|
|
* @return Api
|
|
*/
|
|
public function setColumns($columns) {
|
|
|
|
if( is_array($columns) ) {
|
|
$this->_namespaces[$this->_current_namaspace]['columns'] = $columns;
|
|
} else {
|
|
$this->_error('Columns musst be given as array.');
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set conditions for data
|
|
*
|
|
* @param array $conditions
|
|
* @return Api
|
|
*/
|
|
public function setWhere($conditions) {
|
|
|
|
if( is_array($conditions) ) {
|
|
$this->_namespaces[$this->_current_namaspace]['conditions'] = $conditions;
|
|
} else {
|
|
$this->_error('Where conditions musst be given as array.');
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Output data
|
|
*
|
|
* @access public
|
|
* @param arra $route
|
|
*/
|
|
public function render($route) {
|
|
if( ! isset($route['namespace']) || ! isset($this->_namespaces[$route['namespace']]) || strlen($route['namespace']) == 0 ) {
|
|
$this->_error('No namespace found.');
|
|
}
|
|
|
|
$this->_render_namepsace = $route['namespace'];
|
|
|
|
// secure input
|
|
foreach($route as $key => $value) {
|
|
$route[strip_tags($key)] = strip_tags($value);
|
|
}
|
|
|
|
$this->_route_data = $route;
|
|
|
|
// check if connection is working
|
|
if( ! mysql_ping($this->_db) ) {
|
|
$this->_error('Database connection is not working.');
|
|
}
|
|
|
|
// check if request limit is reached
|
|
if( ! $this->_check_key() ) {
|
|
$this->_error('Soory, but your api key is not valid.');
|
|
}
|
|
|
|
// check if request limit is reached
|
|
if( ! $this->_check_request() ) {
|
|
$this->_error('Soory, you reached the maximum request limit per hour ('.$this->_namespaces[$this->_render_namepsace]['limit'].').');
|
|
}
|
|
|
|
// get data from mysql table
|
|
if( ! $this->_create() ) {
|
|
$this->_error('Soory, but the system was not able to generate data.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the user key is valid
|
|
*
|
|
* @access private
|
|
* @return boolean
|
|
*/
|
|
private function _check_key() {
|
|
if( $this->_namespaces[$this->_render_namepsace]['key'] == true ) {
|
|
$result = mysql_query('SELECT id FROM api_keys WHERE hash = "'.mysql_real_escape_string($this->_route_data['key']).'"', $this->_db) or die(mysql_error());
|
|
|
|
if( mysql_num_rows($result) == false ) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if request limit is reached
|
|
*
|
|
* @access prviate
|
|
* @return boolean
|
|
*/
|
|
private function _check_request() {
|
|
|
|
if( $this->_namespaces[$this->_render_namepsace]['limit'] != false ) {
|
|
// check request limit
|
|
|
|
// delete old requests
|
|
if( $this->_namespaces[$this->_render_namepsace]['key'] == true ) {
|
|
// request limit with key
|
|
|
|
// delete old requests
|
|
mysql_query('DELETE FROM api_requests WHERE hash = "'.mysql_real_escape_string($this->_route_data['key']).'" AND ((UNIX_TIMESTAMP() - time) > 3600) AND namespace = "'.$this->_render_namepsace.'"', $this->_db) or die(mysql_error());
|
|
|
|
// count stored requests
|
|
$result = mysql_query('SELECT COUNT(time) AS requests FROM api_requests WHERE hash = "'.mysql_real_escape_string($this->_route_data['key']).'" AND ((UNIX_TIMESTAMP() - time) < 3600) AND namespace = "'.$this->_render_namepsace.'"', $this->_db) or die(mysql_error());
|
|
|
|
$total_requests = mysql_fetch_array($result);
|
|
|
|
if( $total_requests['requests'] >= $this->_namespaces[$this->_render_namepsace]['limit'] ) {
|
|
// request limit reached
|
|
return false;
|
|
} else {
|
|
|
|
// insert new request
|
|
mysql_query('INSERT INTO api_requests (namespace, hash, ip, time) VALUES("'.$this->_render_namepsace.'", "'.mysql_real_escape_string($this->_route_data['key']).'", "'.$_SERVER['REMOTE_ADDR'].'", "'.time().'")', $this->_db) or die(mysql_error());
|
|
|
|
return true;
|
|
}
|
|
|
|
} else {
|
|
// request limit without key
|
|
|
|
// delete old requests
|
|
mysql_query('DELETE FROM api_requests WHERE ((UNIX_TIMESTAMP() - time) > 3600) AND namespace = "'.$this->_render_namepsace.'"', $this->_db) or die(mysql_error());
|
|
|
|
// count stored requests
|
|
$result = mysql_query('SELECT COUNT(time) AS requests FROM api_requests WHERE ((UNIX_TIMESTAMP() - time) < 3600) AND namespace = "'.$this->_render_namepsace.'"', $this->_db) or die(mysql_error());
|
|
|
|
$total_requests = mysql_fetch_array($result);
|
|
|
|
if( $total_requests['requests'] >= $this->_namespaces[$this->_render_namepsace]['limit'] ) {
|
|
// request limit reached
|
|
return false;
|
|
} else {
|
|
|
|
// insert new request
|
|
mysql_query('INSERT INTO api_requests (namespace, ip, time) VALUES("'.$this->_render_namepsace.'", "'.$_SERVER['REMOTE_ADDR'].'", "'.time().'")', $this->_db) or die(mysql_error());
|
|
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
// no limit is set
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Array to json
|
|
*
|
|
* @access private
|
|
* @return json
|
|
*/
|
|
private function _write_json() {
|
|
|
|
header("Content-Type: application/json");
|
|
|
|
return json_encode($this->_result);
|
|
}
|
|
|
|
/**
|
|
* Array to xml
|
|
*
|
|
* @access private
|
|
* @return string
|
|
*/
|
|
private function _write_xml() {
|
|
|
|
header("Content-Type: application/xml");
|
|
|
|
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
|
|
|
$xml .= "<root>";
|
|
|
|
foreach($this->_result as $key => $value) {
|
|
$xml .= "<{$this->_render_namepsace}>\n";
|
|
|
|
foreach($value as $k => $d) {
|
|
$xml .= "<{$k}>{$d}</{$k}>\n";
|
|
}
|
|
|
|
$xml .= "</{$this->_render_namepsace}>\n";
|
|
}
|
|
|
|
$xml .= "</root>";
|
|
|
|
return $xml;
|
|
}
|
|
|
|
/**
|
|
* Array to csv
|
|
*
|
|
* @access private
|
|
* @return string
|
|
*/
|
|
private function _write_csv() {
|
|
$lines = array();
|
|
|
|
foreach ($this->_result as $key => $value) {
|
|
|
|
$lines[] = implode(';', array_filter($value, function($var) {
|
|
return $var;
|
|
}));
|
|
|
|
}
|
|
|
|
return implode("\n", $lines);
|
|
}
|
|
|
|
/**
|
|
* Array to yaml
|
|
*
|
|
* @access private
|
|
* @return string
|
|
*/
|
|
private function _write_yaml() {
|
|
$yaml = "---\n";
|
|
|
|
$yaml .= "{$this->_render_namepsace}: \n";
|
|
|
|
foreach($this->_result as $key => $value) {
|
|
$yaml .= "-";
|
|
$count = 0;
|
|
|
|
foreach($value as $k => $d) {
|
|
if( $count != 0 ) {
|
|
$yaml .= " ";
|
|
}
|
|
|
|
$yaml .= " {$k}: {$d} \n";
|
|
$count++;
|
|
}
|
|
}
|
|
|
|
return $yaml;
|
|
}
|
|
|
|
/**
|
|
* Gets data from database
|
|
*
|
|
* @access private
|
|
*/
|
|
private function _get() {
|
|
|
|
foreach($this->_namespaces[$this->_render_namepsace]['columns'] as $c) {
|
|
$columns .= $c.', ';
|
|
}
|
|
|
|
$sql = 'SELECT '.substr($columns, 0, -2).' FROM ' .
|
|
$this->_namespaces[$this->_render_namepsace]['table'];
|
|
|
|
if( $this->_namespaces[$this->_render_namepsace]['conditions'] != false ) {
|
|
$sql .= ' WHERE ';
|
|
|
|
foreach($this->_namespaces[$this->_render_namepsace]['conditions'] as $where => $data) {
|
|
|
|
if( ! isset($this->_route_data[$data['route']]) ) {
|
|
$this->_error('Route '.$data['route'].' is required.');
|
|
}
|
|
|
|
// validate
|
|
if( isset($data['rule']) ) {
|
|
if( ! in_array($data['rule'], $this->_rules) ) {
|
|
$this->_error('Rule '.$data['rule'].' is not avaiable.');
|
|
}
|
|
|
|
$rule = '_'.$data['rule'];
|
|
|
|
if( ! $this->$rule($this->_route_data[$data['route']]) ) {
|
|
$this->_error('Wrong route format.');
|
|
}
|
|
}
|
|
|
|
$sql .= $where.' = "'.mysql_real_escape_string($this->_route_data[$data['route']]).'"';
|
|
|
|
if( isset($data['connect']) ) {
|
|
$sql .= ' '.$data['connect'];
|
|
}
|
|
}
|
|
}
|
|
|
|
if( $this->_namespaces[$this->_render_namepsace]['sort'] != false ) {
|
|
$sql .= ' ORDER BY ' . $this->_namespaces[$this->_render_namepsace]['sort'];
|
|
}
|
|
|
|
if( $this->_namespaces[$this->_render_namepsace]['request'] != false ) {
|
|
$sql .= ' LIMIT ' . $this->_namespaces[$this->_render_namepsace]['request'];
|
|
}
|
|
|
|
$result = mysql_query($sql, $this->_db) or die(mysql_error());
|
|
|
|
if( mysql_num_rows($result) == false ) {
|
|
$this->_error('No results found.');
|
|
}
|
|
|
|
// write data to a nice array
|
|
for($i = 0; $i < mysql_num_rows($result); $i++)
|
|
{
|
|
$dump[$i] = mysql_fetch_array($result, MYSQL_ASSOC);
|
|
}
|
|
|
|
$this->_result = $dump;
|
|
}
|
|
|
|
private function _alpha_numeric($validate) {
|
|
if( ctype_alnum($validate) ) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function _numeric($validate) {
|
|
if( ctype_digit($validate) ) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
private function _create() {
|
|
|
|
|
|
if( $this->_namespaces[$this->_render_namepsace]['cache'] == false ) {
|
|
|
|
$this->_get();
|
|
|
|
} else {
|
|
// cache is turned on
|
|
if( $this->_namespaces[$this->_render_namepsace]['key'] == false ) {
|
|
// key musst truned off
|
|
|
|
$file = $this->_namespaces[$this->_render_namepsace]['cache']['folder'].sha1($this->_render_namepsace).'.txt';
|
|
|
|
if( ! file_exists($file) || (time() - filemtime($file) > $this->_namespaces[$this->_render_namepsace]['cache']['time']) ) {
|
|
|
|
// write to cache and delete old file
|
|
@unlink($file);
|
|
|
|
$f = fopen($file, 'w');
|
|
|
|
$this->_get();
|
|
|
|
$cache = new stdClass;
|
|
$cache->result = $this->_result;
|
|
|
|
fwrite($f, serialize($cache));
|
|
|
|
fclose($f);
|
|
}
|
|
|
|
// read from cache
|
|
$data = unserialize(file_get_contents($file));
|
|
|
|
$this->_result = $data->result;
|
|
|
|
|
|
} else {
|
|
$this->_namespaces[$this->_render_namepsace]['cache'] = false;
|
|
|
|
$this->_create();
|
|
}
|
|
}
|
|
|
|
$type_function = '_write_';
|
|
|
|
if( is_array($this->_namespaces[$this->_render_namepsace]['output']) ) {
|
|
if( isset($this->_route_data['output']) ) {
|
|
$type_function .= $this->_route_data['output'];
|
|
} else {
|
|
$type_function .= $this->_namespaces[$this->_render_namepsace]['output'][0];
|
|
}
|
|
} else {
|
|
$type_function .= $this->_namespaces[$this->_render_namepsace]['output'];
|
|
}
|
|
|
|
|
|
die($this->$type_function());
|
|
|
|
}
|
|
}
|
|
?>
|