Update vendor

This commit is contained in:
Frederic Guillot 2015-06-21 09:56:36 -04:00
parent be89b3e9fa
commit 154abcd57b
74 changed files with 1192 additions and 361 deletions

View File

@ -3,10 +3,10 @@
"preferred-install": "dist"
},
"require": {
"fguillot/simple-validator": "dev-master",
"fguillot/json-rpc": "dev-master",
"fguillot/picodb": "0.0.2",
"fguillot/picofeed": "dev-master",
"fguillot/simple-validator": "v0.0.3",
"fguillot/json-rpc": "v0.0.3",
"fguillot/picodb": "v0.0.3",
"fguillot/picofeed": "v0.1.4",
"fguillot/picofarad": "dev-master"
},
"autoload": {

2
vendor/autoload.php vendored
View File

@ -4,4 +4,4 @@
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInitf813f763f87d109605f7c55600b459d5::getLoader();
return ComposerAutoloaderInit3973a10bce966c867b4a041e7bc913b0::getLoader();

View File

@ -54,8 +54,6 @@ class ClassLoader
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
@ -250,27 +248,6 @@ class ClassLoader
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* Registers this instance as an autoloader.
*
@ -322,9 +299,6 @@ class ClassLoader
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative) {
return false;
}
$file = $this->findFileWithExtension($class, '.php');

View File

@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'JsonRPC\\AuthenticationFailure' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Server.php',
'JsonRPC\\Client' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Client.php',
'JsonRPC\\InvalidJsonFormat' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Server.php',
'JsonRPC\\InvalidJsonRpcFormat' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Server.php',

View File

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitf813f763f87d109605f7c55600b459d5
class ComposerAutoloaderInit3973a10bce966c867b4a041e7bc913b0
{
private static $loader;
@ -19,9 +19,9 @@ class ComposerAutoloaderInitf813f763f87d109605f7c55600b459d5
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInitf813f763f87d109605f7c55600b459d5', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInit3973a10bce966c867b4a041e7bc913b0', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInitf813f763f87d109605f7c55600b459d5', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInit3973a10bce966c867b4a041e7bc913b0', 'loadClassLoader'));
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
@ -42,14 +42,14 @@ class ComposerAutoloaderInitf813f763f87d109605f7c55600b459d5
$includeFiles = require __DIR__ . '/autoload_files.php';
foreach ($includeFiles as $file) {
composerRequiref813f763f87d109605f7c55600b459d5($file);
composerRequire3973a10bce966c867b4a041e7bc913b0($file);
}
return $loader;
}
}
function composerRequiref813f763f87d109605f7c55600b459d5($file)
function composerRequire3973a10bce966c867b4a041e7bc913b0($file)
{
require $file;
}

View File

@ -1,43 +1,4 @@
[
{
"name": "fguillot/picodb",
"version": "v0.0.2",
"version_normalized": "0.0.2.0",
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoDb.git",
"reference": "b3ef5f79e7e5e33729fdbf9c02c8a252a3d76b6b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fguillot/picoDb/zipball/b3ef5f79e7e5e33729fdbf9c02c8a252a3d76b6b",
"reference": "b3ef5f79e7e5e33729fdbf9c02c8a252a3d76b6b",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2015-01-25 16:20:14",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"PicoDb": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"WTFPL"
],
"authors": [
{
"name": "Frédéric Guillot",
"homepage": "http://fredericguillot.com"
}
],
"description": "Minimalist database query builder",
"homepage": "https://github.com/fguillot/picoDb"
},
{
"name": "fguillot/picofarad",
"version": "dev-master",
@ -79,8 +40,8 @@
},
{
"name": "fguillot/simple-validator",
"version": "dev-master",
"version_normalized": "9999999-dev",
"version": "v0.0.3",
"version_normalized": "0.0.3.0",
"source": {
"type": "git",
"url": "https://github.com/fguillot/simpleValidator.git",
@ -117,23 +78,23 @@
},
{
"name": "fguillot/json-rpc",
"version": "dev-master",
"version_normalized": "9999999-dev",
"version": "v0.0.3",
"version_normalized": "0.0.3.0",
"source": {
"type": "git",
"url": "https://github.com/fguillot/JsonRPC.git",
"reference": "1a397be7739ddabba87b07f0354655bd91087518"
"reference": "ef2f1aa1c07f0e3e8878c53b3a5fc81daedbebb8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/1a397be7739ddabba87b07f0354655bd91087518",
"reference": "1a397be7739ddabba87b07f0354655bd91087518",
"url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/ef2f1aa1c07f0e3e8878c53b3a5fc81daedbebb8",
"reference": "ef2f1aa1c07f0e3e8878c53b3a5fc81daedbebb8",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2015-04-14 01:50:16",
"time": "2015-05-20 15:08:40",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -155,18 +116,57 @@
"homepage": "https://github.com/fguillot/JsonRPC"
},
{
"name": "fguillot/picofeed",
"version": "dev-master",
"version_normalized": "9999999-dev",
"name": "fguillot/picodb",
"version": "v0.0.3",
"version_normalized": "0.0.3.0",
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoFeed.git",
"reference": "a6087e8264550891c1b8a6da77eca0cab9328709"
"url": "https://github.com/fguillot/picoDb.git",
"reference": "f65d11cb52de34e0fd236a34184ca1a310da244a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fguillot/picoFeed/zipball/a6087e8264550891c1b8a6da77eca0cab9328709",
"reference": "a6087e8264550891c1b8a6da77eca0cab9328709",
"url": "https://api.github.com/repos/fguillot/picoDb/zipball/f65d11cb52de34e0fd236a34184ca1a310da244a",
"reference": "f65d11cb52de34e0fd236a34184ca1a310da244a",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2015-05-17 23:57:05",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"PicoDb": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frédéric Guillot",
"homepage": "http://fredericguillot.com"
}
],
"description": "Minimalist database query builder",
"homepage": "https://github.com/fguillot/picoDb"
},
{
"name": "fguillot/picofeed",
"version": "v0.1.4",
"version_normalized": "0.1.4.0",
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoFeed.git",
"reference": "efa4a3ff139d147ac294070d8b60005abefa19ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fguillot/picoFeed/zipball/efa4a3ff139d147ac294070d8b60005abefa19ad",
"reference": "efa4a3ff139d147ac294070d8b60005abefa19ad",
"shasum": ""
},
"require": {
@ -180,7 +180,7 @@
"suggest": {
"ext-curl": "PicoFeed will use cURL if present"
},
"time": "2015-04-27 22:22:06",
"time": "2015-06-21 13:45:50",
"bin": [
"picofeed"
],

View File

@ -57,6 +57,8 @@ $server->register('random', function ($start, $end) {
// Return the response to the client
echo $server->execute();
?>
```
Class/Method binding:
@ -85,9 +87,57 @@ $server->bind('mySecondProcedure', new Api, 'doSomething');
// The procedure and the method are the same
$server->bind('doSomething', 'Api');
// Attach the class, client will be able to call directly Api::doSomething()
$server->attach(new Api);
echo $server->execute();
?>
```
Before callback:
Before each procedure execution, a custom method can be called.
This method receive the following arguments: `$username, $password, $class, $method`.
```php
<?php
use JsonRPC\Server;
use JsonRPC\AuthenticationFailure;
class Api
{
public function beforeProcedure($username, $password, $class, $method)
{
if ($login_condition_failed) {
throw new AuthenticationFailure('Wrong credentials!');
}
}
public function addition($a, $b)
{
return $a + $b;
}
}
$server = new Server;
$server->authentication(['myuser' => 'mypassword']);
// Register the before callback
$server->before('beforeProcedure');
$server->attach(new Api);
echo $server->execute();
?>
```
You can use this method to implements a custom authentication system or anything else.
If you would like to reject the authentication, you can throw the exception `JsonRPC\AuthenticationFailure`.
### Client
Example with positional parameters:
@ -108,8 +158,6 @@ Example with named arguments:
```php
<?php
require 'JsonRPC/Client.php';
use JsonRPC\Client;
$client = new Client('http://localhost/server.php');
@ -166,7 +214,7 @@ All results are stored at the same position of the call.
- `BadFunctionCallException`: Procedure not found on the server
- `InvalidArgumentException`: Wrong procedure arguments
- `RuntimeException`: Protocol error
- `RuntimeException`: Protocol error, authentication failure or connection failure, the message describe the exact error
### Enable client debugging
@ -240,7 +288,7 @@ use JsonRPC\Server;
$server = new Server;
// List of users to allow
$server->authentication(['jsonrpc' => 'toto']);
$server->authentication(['user1' => 'password1', 'user2' => 'password2']);
// Procedures registration
@ -258,7 +306,57 @@ On the client, set credentials like that:
use JsonRPC\Client;
$client = new Client('http://localhost/server.php');
$client->authentication('jsonrpc', 'toto');
$client->authentication('user1', 'password1');
```
If the authentication failed, the client throw a RuntimeException.
Using an alternative authentication header:
```php
use JsonRPC\Server;
$server = new Server;
$server->setAuthenticationHeader('X-Authentication');
$server->authentication(['myusername' => 'mypassword']);
```
The example above will use the HTTP header `X-Authentication` instead of the standard `Authorization: Basic [BASE64_CREDENTIALS]`.
The username/password values need be encoded in base64: `base64_encode('username:password')`.
### Exceptions
If you want to send an error to the client you can throw an exception.
You should configure which exceptions should be relayed to the client first:
```php
<?php
use JsonRPC\Server;
class MyException extends RuntimeException {};
$server = new Server;
// Exceptions that should be relayed to the client, if they occur
$server->attachException('MyException');
// Procedures registration
[...]
// Return the response to the client
echo $server->execute();
```
Then you can throw that exception inside your procedure:
```
throw new MyException("An error occured", 123);
```
To relay all exceptions regardless of type, leave out the exception class name:
```
$server->attachException();
```

View File

@ -23,6 +23,15 @@ class Client
*/
private $url;
/**
* If the only argument passed to a function is an array
* assume it contains named arguments
*
* @access public
* @var boolean
*/
public $named_arguments = true;
/**
* HTTP client timeout
*
@ -78,7 +87,6 @@ class Client
* @var array
*/
private $headers = array(
'Connection: close',
'Content-Type: application/json',
'Accept: application/json'
);
@ -89,6 +97,13 @@ class Client
*/
public $ssl_verify_peer = true;
/**
* cURL handle
*
* @access private
*/
private $ch;
/**
* Constructor
*
@ -102,6 +117,17 @@ class Client
$this->url = $url;
$this->timeout = $timeout;
$this->headers = array_merge($this->headers, $headers);
$this->ch = curl_init();
}
/**
* Destructor
*
* @access public
*/
public function __destruct()
{
curl_close($this->ch);
}
/**
@ -115,7 +141,7 @@ class Client
public function __call($method, array $params)
{
// Allow to pass an array and use named arguments
if (count($params) === 1 && is_array($params[0])) {
if ($this->named_arguments && count($params) === 1 && is_array($params[0])) {
$params = $params[0];
}
@ -284,25 +310,29 @@ class Client
*/
public function doRequest($payload)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
curl_setopt($ch, CURLOPT_USERAGENT, 'JSON-RPC PHP Client');
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->ssl_verify_peer);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt_array($this->ch, array(
CURLOPT_URL => $this->url,
CURLOPT_HEADER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => $this->timeout,
CURLOPT_USERAGENT => 'JSON-RPC PHP Client',
CURLOPT_HTTPHEADER => $this->headers,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_SSL_VERIFYPEER => $this->ssl_verify_peer,
CURLOPT_POSTFIELDS => json_encode($payload)
));
if ($this->username && $this->password) {
curl_setopt($ch, CURLOPT_USERPWD, $this->username.':'.$this->password);
curl_setopt($this->ch, CURLOPT_USERPWD, $this->username.':'.$this->password);
}
$http_body = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$http_body = curl_exec($this->ch);
$http_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
if (curl_errno($this->ch)) {
throw new RuntimeException(curl_error($this->ch));
}
if ($http_code === 401 || $http_code === 403) {
throw new RuntimeException('Access denied');
@ -315,8 +345,6 @@ class Client
error_log('==> Response: '.PHP_EOL.json_encode($response, JSON_PRETTY_PRINT));
}
curl_close($ch);
return is_array($response) ? $response : array();
}
}

View File

@ -12,6 +12,7 @@ use ReflectionMethod;
class InvalidJsonRpcFormat extends Exception {};
class InvalidJsonFormat extends Exception {};
class AuthenticationFailure extends Exception {};
/**
* JsonRPC server class
@ -33,7 +34,6 @@ class Server
/**
* List of procedures
*
* @static
* @access private
* @var array
*/
@ -42,7 +42,6 @@ class Server
/**
* List of classes
*
* @static
* @access private
* @var array
*/
@ -51,12 +50,43 @@ class Server
/**
* List of instances
*
* @static
* @access private
* @var array
*/
private $instances = array();
/**
* List of exception classes that should be relayed to client
*
* @access private
* @var array
*/
private $exceptions = array();
/**
* Method name to execute before the procedure
*
* @access private
* @var string
*/
private $before = '';
/**
* Username
*
* @access private
* @var string
*/
private $username = '';
/**
* Password
*
* @access private
* @var string
*/
private $password = '';
/**
* Constructor
*
@ -70,6 +100,78 @@ class Server
$this->payload = $payload;
$this->callbacks = $callbacks;
$this->classes = $classes;
}
/**
* Define alternative authentication header
*
* @access public
* @param string $header Header name
* @return Server
*/
public function setAuthenticationHeader($header)
{
if (! empty($header)) {
$header = 'HTTP_'.str_replace('-', '_', strtoupper($header));
if (isset($_SERVER[$header])) {
list($this->username, $this->password) = explode(':', @base64_decode($_SERVER[$header]));
}
}
return $this;
}
/**
* Get username
*
* @access public
* @return string
*/
public function getUsername()
{
return $this->username ?: @$_SERVER['PHP_AUTH_USER'];
}
/**
* Get password
*
* @access public
* @return string
*/
public function getPassword()
{
return $this->password ?: @$_SERVER['PHP_AUTH_PW'];
}
/**
* Send authentication failure response
*
* @access public
*/
public function sendAuthenticationFailureResponse()
{
header('WWW-Authenticate: Basic realm="JsonRPC"');
header('Content-Type: application/json');
header('HTTP/1.0 401 Unauthorized');
echo '{"error": "Authentication failed"}';
exit;
}
/**
* Send forbidden response
*
* @access public
*/
public function sendForbiddenResponse()
{
header('Content-Type: application/json');
header('HTTP/1.0 403 Forbidden');
echo '{"error": "Access Forbidden"}';
exit;
}
/**
@ -80,14 +182,10 @@ class Server
* @access public
* @param array $hosts List of hosts
*/
public function allowHosts(array $hosts) {
public function allowHosts(array $hosts)
{
if (! in_array($_SERVER['REMOTE_ADDR'], $hosts)) {
header('Content-Type: application/json');
header('HTTP/1.0 403 Forbidden');
echo '{"error": "Access Forbidden"}';
exit;
$this->sendForbiddenResponse();
}
}
@ -98,19 +196,15 @@ class Server
*
* @access public
* @param array $users Map of username/password
* @return Server
*/
public function authentication(array $users)
{
if (! isset($_SERVER['PHP_AUTH_USER']) ||
! isset($users[$_SERVER['PHP_AUTH_USER']]) ||
$users[$_SERVER['PHP_AUTH_USER']] !== $_SERVER['PHP_AUTH_PW']) {
header('WWW-Authenticate: Basic realm="JsonRPC"');
header('Content-Type: application/json');
header('HTTP/1.0 401 Unauthorized');
echo '{"error": "Authentication failed"}';
exit;
if (! isset($users[$this->getUsername()]) || $users[$this->getUsername()] !== $this->getPassword()) {
$this->sendAuthenticationFailureResponse();
}
return $this;
}
/**
@ -119,10 +213,12 @@ class Server
* @access public
* @param string $procedure Procedure name
* @param closure $callback Callback
* @return Server
*/
public function register($name, Closure $callback)
{
$this->callbacks[$name] = $callback;
return $this;
}
/**
@ -132,6 +228,7 @@ class Server
* @param string $procedure Procedure name
* @param mixed $class Class name or instance
* @param string $method Procedure name
* @return Server
*/
public function bind($procedure, $class, $method = '')
{
@ -140,6 +237,7 @@ class Server
}
$this->classes[$procedure] = array($class, $method);
return $this;
}
/**
@ -147,10 +245,39 @@ class Server
*
* @access public
* @param mixed $instance Instance name
* @return Server
*/
public function attach($instance)
{
$this->instances[] = $instance;
return $this;
}
/**
* Bind an exception
* If this exception occurs it is relayed to the client as JSON-RPC error
*
* @access public
* @param mixed $exception Exception class. Defaults to all.
* @return Server
*/
public function attachException($exception = 'Exception')
{
$this->exceptions[] = $exception;
return $this;
}
/**
* Attach a method that will be called before the procedure
*
* @access public
* @param string $before
* @return Server
*/
public function before($before)
{
$this->before = $before;
return $this;
}
/**
@ -327,6 +454,25 @@ class Server
$this->payload
);
}
catch (AuthenticationFailure $e) {
$this->sendAuthenticationFailureResponse();
}
catch (Exception $e) {
foreach ($this->exceptions as $class) {
if ($e instanceof $class) {
return $this->getResponse(array(
'error' => array(
'code' => $e->getCode(),
'message' => $e->getMessage()
)),
$this->payload
);
}
}
throw $e;
}
}
/**
@ -342,7 +488,7 @@ class Server
if (isset($this->callbacks[$procedure])) {
return $this->executeCallback($this->callbacks[$procedure], $params);
}
else if (isset($this->classes[$procedure])) {
else if (isset($this->classes[$procedure]) && method_exists($this->classes[$procedure][0], $this->classes[$procedure][1])) {
return $this->executeMethod($this->classes[$procedure][0], $this->classes[$procedure][1], $params);
}
@ -388,6 +534,13 @@ class Server
*/
public function executeMethod($class, $method, $params)
{
$instance = is_string($class) ? new $class : $class;
// Execute before action
if (! empty($this->before) && method_exists($instance, $this->before)) {
$instance->{$this->before}($this->getUsername(), $this->getPassword(), get_class($class), $method);
}
$reflection = new ReflectionMethod($class, $method);
$arguments = $this->getArguments(
@ -397,10 +550,7 @@ class Server
$reflection->getNumberOfParameters()
);
return $reflection->invokeArgs(
is_string($class) ? new $class : $class,
$arguments
);
return $reflection->invokeArgs($instance, $arguments);
}
/**

View File

@ -40,7 +40,7 @@ class ServerProcedureTest extends PHPUnit_Framework_TestCase
}
/**
* @expectedException ReflectionException
* @expectedException BadFunctionCallException
*/
public function testClassNotFound()
{
@ -50,7 +50,7 @@ class ServerProcedureTest extends PHPUnit_Framework_TestCase
}
/**
* @expectedException ReflectionException
* @expectedException BadFunctionCallException
*/
public function testMethodNotFound()
{

21
vendor/fguillot/picodb/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Frederic Guillot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -12,7 +12,7 @@ Features
- Requires only PDO
- Use prepared statements
- Handle schema versions (migrations)
- License: [WTFPL](http://www.wtfpl.net)
- License: MIT
Requirements
------------
@ -124,7 +124,7 @@ or
$db->table('toto')->desc('column1')->findAll();
```
or
or
```php
#db->table('toto')->orderBy('column1', 'ASC')->findAll();

View File

@ -3,7 +3,7 @@
"description": "Minimalist database query builder",
"homepage": "https://github.com/fguillot/picoDb",
"type": "library",
"license": "WTFPL",
"license": "MIT",
"authors": [
{
"name": "Frédéric Guillot",

View File

@ -248,7 +248,7 @@ class Database
$this->setLogMessage($e->getMessage());
throw new RuntimeException('SQL error');
throw new RuntimeException('SQL error'.($this->log_queries ? ': '.$e->getMessage() : ''));
}
}

View File

@ -27,6 +27,10 @@ class Mysql extends PDO
$dsn = 'mysql:host='.$settings['hostname'].';dbname='.$settings['database'].';charset='.$settings['charset'];
if (! empty($settings['port'])) {
$dsn .= ';port='.$settings['port'];
}
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET sql_mode = STRICT_ALL_TABLES',
);

View File

@ -26,6 +26,10 @@ class Postgres extends PDO
$dsn = 'pgsql:host='.$settings['hostname'].';dbname='.$settings['database'];
if (! empty($settings['port'])) {
$dsn .= ';port='.$settings['port'];
}
parent::__construct($dsn, $settings['username'], $settings['password']);
if (isset($settings['schema_table'])) {
@ -66,7 +70,7 @@ class Postgres extends PDO
public function escapeIdentifier($value)
{
return $value;
return '"'.$value.'"';
}
public function operatorLikeCaseSensitive()

View File

@ -13,16 +13,22 @@ class Table
protected $table_name = '';
protected $values = array();
private $columns = array();
private $sql_limit = '';
private $sql_offset = '';
private $sql_order = '';
private $joins = array();
private $condition = '';
private $conditions = array();
private $or_conditions = array();
private $is_or_condition = false;
private $columns = array();
private $distinct = false;
private $group_by = array();
private $filter_callback = null;
/**
@ -82,7 +88,7 @@ class Table
'UPDATE %s SET %s %s',
$this->db->escapeIdentifier($this->table_name),
implode(', ', $columns),
$this->conditions()
$this->buildCondition()
);
return $this->db->execute($sql, $values) !== false;
@ -124,7 +130,7 @@ class Table
$sql = sprintf(
'DELETE FROM %s %s',
$this->db->escapeIdentifier($this->table_name),
$this->conditions()
$this->buildCondition()
);
$result = $this->db->execute($sql, $this->values);
@ -155,7 +161,7 @@ class Table
$rq = $this->db->execute($this->buildSelectQuery(), $this->values);
$results = $rq->fetchAll(PDO::FETCH_ASSOC);
if (is_callable($this->filter_callback)) {
if (is_callable($this->filter_callback) && ! empty($results)) {
return call_user_func($this->filter_callback, $results);
}
@ -227,7 +233,7 @@ class Table
empty($this->columns) ? '*' : implode(', ', $this->columns),
$this->db->escapeIdentifier($this->table_name),
implode(' ', $this->joins),
$this->conditions(),
$this->buildCondition(),
empty($this->group_by) ? '' : 'GROUP BY '.implode(', ', $this->group_by),
$this->sql_order,
$this->sql_limit,
@ -244,16 +250,37 @@ class Table
public function count()
{
$sql = sprintf(
'SELECT COUNT(*) FROM %s '.implode(' ', $this->joins).$this->conditions().$this->sql_order.$this->sql_limit.$this->sql_offset,
'SELECT COUNT(*) FROM %s '.implode(' ', $this->joins).$this->buildCondition().$this->sql_order.$this->sql_limit.$this->sql_offset,
$this->db->escapeIdentifier($this->table_name)
);
$rq = $this->db->execute($sql, $this->values);
$result = $rq->fetchColumn();
return $result ? (int) $result : 0;
}
/**
* Sum
*
* @access public
* @param string $column
* @return float
*/
public function sum($column)
{
$sql = sprintf(
'SELECT SUM(%s) FROM %s '.implode(' ', $this->joins).$this->buildCondition().$this->sql_order.$this->sql_limit.$this->sql_offset,
$this->db->escapeIdentifier($column),
$this->db->escapeIdentifier($this->table_name)
);
$rq = $this->db->execute($sql, $this->values);
$result = $rq->fetchColumn();
return $result ? (float) $result : 0;
}
/**
* Left join
*
@ -262,14 +289,15 @@ class Table
* @param string $foreign_column Foreign key on the join table
* @param string $local_column Local column
* @param string $local_table Local table
* @param string $alias Join table alias
* @return \PicoDb\Table
*/
public function join($table, $foreign_column, $local_column, $local_table = '')
public function join($table, $foreign_column, $local_column, $local_table = '', $alias = '')
{
$this->joins[] = sprintf(
'LEFT JOIN %s ON %s=%s',
$this->db->escapeIdentifier($table),
$this->db->escapeIdentifier($table).'.'.$this->db->escapeIdentifier($foreign_column),
$this->db->escapeIdentifier($alias ?: $table).'.'.$this->db->escapeIdentifier($foreign_column),
$this->db->escapeIdentifier($local_table ?: $this->table_name).'.'.$this->db->escapeIdentifier($local_column)
);
@ -277,13 +305,53 @@ class Table
}
/**
* Build conditions
* Left join
*
* @access public
* @param string $table1
* @param string $alias1
* @param string $column1
* @param string $table2
* @param string $column2
* @return \PicoDb\Table
*/
public function left($table1, $alias1, $column1, $table2, $column2)
{
$this->joins[] = sprintf(
'LEFT JOIN %s AS %s ON %s=%s',
$this->db->escapeIdentifier($table1),
$this->db->escapeIdentifier($alias1),
$this->db->escapeIdentifier($alias1).'.'.$this->db->escapeIdentifier($column1),
$this->db->escapeIdentifier($table2).'.'.$this->db->escapeIdentifier($column2)
);
return $this;
}
/**
* Add custom condition
*
* @access private
* @return Table
*/
private function condition($condition)
{
$this->condition = $condition;
return $this;
}
/**
* Build condition
*
* @access private
* @return string
*/
public function conditions()
private function buildCondition()
{
if (! empty($this->condition)) {
return 'WHERE '.$this->condition;
}
return empty($this->conditions) ? '' : ' WHERE '.implode(' AND ', $this->conditions);
}
@ -292,6 +360,7 @@ class Table
*
* @access public
* @param string $sql
* @return Table
*/
public function addCondition($sql)
{
@ -301,6 +370,8 @@ class Table
else {
$this->conditions[] = $sql;
}
return $this;
}
/**

View File

@ -21,7 +21,7 @@ PicoFeed will try first to find the favicon from the meta tags and fallback to t
When the HTML page is parsed, relative links and protocol relative links are converted to absolute url.
Download a know favicon
Download a known favicon
-----------------------
It's possible to download a known favicon using the second optional parameter of Favicon::find(). The link to the favicon can be a relative or protocol relative url as well, but it has to be relative to the specified website.
@ -93,4 +93,4 @@ $config->setClientUserAgent('My RSS Reader');
$favicon = new Favicon($config);
$favicon->find('https://github.com');
```
```

View File

@ -5,7 +5,7 @@ Versions
--------
- Development version: master
- Stable version: v0.1.3
- Stable version: v0.1.4
Installation with Composer
--------------------------
@ -15,7 +15,7 @@ Configure your `composer.json`:
```json
{
"require": {
"fguillot/picofeed": "0.1.3"
"fguillot/picofeed": "0.1.4"
}
}
```
@ -23,7 +23,7 @@ Configure your `composer.json`:
Or simply:
```bash
composer require fguillot/picofeed:0.1.3
composer require fguillot/picofeed:0.1.4
```
And download the code:

View File

@ -62,7 +62,7 @@ class Stream extends Client
{
foreach($headers as $header) {
if (stripos($header, 'Location') === 0) {
list($name, $value) = explode(': ', $header);
list(, $value) = explode(': ', $header);
$this->url = Url::resolve($value, $this->url);
}

View File

@ -15,7 +15,21 @@ class Encoding
return $input;
}
// convert input to utf-8; ignore malformed characters
return iconv($encoding, 'UTF-8//IGNORE', $input);
// suppress all notices since it isn't possible to silence only the
// notice "Wrong charset, conversion from $in_encoding to $out_encoding is not allowed"
set_error_handler(function() {}, E_NOTICE);
// convert input to utf-8 and strip invalid characters
$value = iconv($encoding, 'UTF-8//IGNORE', $input);
// stop silencing of notices
restore_error_handler();
// return input if something went wrong, maybe it's usable anyway
if ($value === false) {
return $input;
}
return $value;
}
}

View File

@ -107,7 +107,7 @@ class Filter
}
/**
* Dirty quickfixes before XML parsing
* Fixes before XML parsing
*
* @static
* @access public
@ -116,17 +116,37 @@ class Filter
*/
public static function normalizeData($data)
{
$invalid_chars = array(
"\x10",
"\xc3\x20",
"&#x1F;",
"\xe2\x80\x9c\x08",
$entities = array(
'/(&#)(\d+);/m', // decimal encoded
'/(&#x)([a-f0-9]+);/mi', // hex encoded
);
foreach ($invalid_chars as $needle) {
$data = str_replace($needle, '', $data);
}
// strip invalid XML 1.0 characters which are encoded as entities
$data = preg_replace_callback($entities, function($matches) {
$code_point = $matches[2];
return $data;
// convert hex entity to decimal
if (strtolower($matches[1]) === '&#x') {
$code_point = hexdec($code_point);
}
$code_point = (int) $code_point;
// replace invalid characters
if ($code_point < 9
|| ($code_point > 10 && $code_point < 13)
|| ($code_point > 13 && $code_point < 32)
|| ($code_point > 55295 && $code_point < 57344)
|| ($code_point > 65533 && $code_point < 65536)
|| $code_point > 1114111
) {
return '';
};
return $matches[0];
}, $data);
// strip every utf-8 character than isn't in the range of valid XML 1.0 characters
return (string) preg_replace('/[^\x{0009}\x{000A}\x{000D}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]/u', '', $data);
}
}

View File

@ -88,7 +88,7 @@ class DateParser
* @access public
* @param string $format Date format
* @param string $value Original date value
* @return DateTime
* @return DateTime|boolean
*/
public function getValidDate($format, $value)
{

View File

@ -117,9 +117,6 @@ abstract class Parser
// Encode everything in UTF-8
Logger::setMessage(get_called_class().': HTTP Encoding "'.$http_encoding.'" ; XML Encoding "'.$xml_encoding.'"');
$this->content = Encoding::convert($this->content, $xml_encoding ?: $http_encoding);
// Workarounds
$this->content = Filter::normalizeData($this->content);
}
/**
@ -135,9 +132,15 @@ abstract class Parser
$xml = XmlParser::getSimpleXml($this->content);
if ($xml === false) {
Logger::setMessage(get_called_class().': XML parsing error');
Logger::setMessage(XmlParser::getErrors());
throw new MalformedXmlException('XML parsing error');
Logger::setMessage(get_called_class().': Applying XML workarounds');
$this->content = Filter::normalizeData($this->content);
$xml = XmlParser::getSimpleXml($this->content);
if ($xml === false) {
Logger::setMessage(get_called_class().': XML parsing error');
Logger::setMessage(XmlParser::getErrors());
throw new MalformedXmlException('XML parsing error');
}
}
$this->namespaces = $xml->getNamespaces(true);

View File

@ -23,7 +23,13 @@ class Rss20 extends Parser
*/
public function getItemsTree(SimpleXMLElement $xml)
{
return $xml->channel->item;
$items = array();
if (isset($xml->channel->item)) {
$items = $xml->channel->item;
}
return $items;
}
/**

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%alt="(.+)" title="(.+)" */>%' => "/><br/>$1<br/>$2"
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,13 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'body' => array(
'//img[@id="comic_image"]',
'//div[@class="comment-wrapper"][position()=1]'
),
'strip' => array(),
'test_url' => 'http://www.anythingcomic.com/comics/2108929/stress-free/',
)
),
);

View File

@ -0,0 +1,13 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'test_url' => 'http://buttersafe.com/2015/04/21/the-incredible-flexible-man/',
'body' => array(
'//div[@id="comic"]',
'//div[@class="post-comic"]',
),
'strip' => array()
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%href="http://www.channelate.com/(\\d+)/(\\d+)/(\\d+)/[^"]*"%' => 'href="http://www.channelate.com/extra-panel/$1$2$3/"'
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,15 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'body' => array('//img[@id="comicimage"]'),
'strip' => array(),
'test_url' => 'http://drawingboardcomic.com/index.php?comic=208',
)
),
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%-\\d+x\\d+%' => "",
)
)
);

View File

@ -35,7 +35,12 @@ return array(
'body' => array('//*[@class="body"]'),
'test_url' => 'http://www.escapistmagazine.com/articles/view/comicsandcosplay/comics/fraughtwithperil/12166-The-Escapist-Presents-Escapist-Comics-Critical-Miss-B-lyeh-Fhlop?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=articles',
'strip' => array()
)
),
'%/articles/view/video-games/columns/.*%' => array(
'body' => array('//*[@id="article_content"]'),
'test_url' => 'http://www.escapistmagazine.com/articles/view/video-games/columns/experienced-points/13971-What-50-Shades-and-Batman-Have-in-Common.2',
'strip' => array()
),
)
);

View File

@ -0,0 +1,15 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'body' => array('//a[@class="comic"]/img'),
'strip' => array(),
'test_url' => 'http://www.exocomics.com/379',
)
),
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -5,6 +5,7 @@ return array(
'test_url' => 'http://explosm.net/comics/3803/',
'body' => array(
'//div[@id="comic-container"]',
'//div[@id="comic-container"]//img/@src'
),
'strip' => array(
),

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%-\\d+x\\d+%' => "",
)
)
);

View File

@ -0,0 +1,12 @@
<?php
return array(
'grabber' => array(
'%/comics/oots.*%' => array(
'test_url' => 'http://www.giantitp.com/comics/oots0989.html',
'body' => array(
'//td[@align="center"]/img'
),
'strip' => array()
)
)
);

View File

@ -0,0 +1,12 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'test_url' => 'http://www.gocomics.com/pearlsbeforeswine/2015/05/30',
'body' => array(
'//div[1]/p[1]/a[1]/img',
),
'strip' => array(),
)
)
);

View File

@ -0,0 +1,18 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'body' => array(
'//div[@id="comic"]',
'//div[@class="entry"]'
),
'strip' => array('//div[@class="ssba"]'),
'test_url' => 'http://www.happletea.com/comic/mans-best-friend/',
)
),
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%(<img.+(\\d{4}-\\d{2}-\\d{2}).+/>)%' => '$1<img src="http://invisiblebread.com/eps/$2-extrapanel.png"/>'
)
)
);

View File

@ -0,0 +1,10 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'body' => array('//span[@class="ccbnTxt"]'),
'strip' => array(),
'test_url' => 'http://ir.amd.com/phoenix.zhtml?c=74093&p=RssLanding&cat=news&id=2055819',
)
),
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%-\\d+x\\d+%' => "",
)
)
);

View File

@ -1,10 +1,8 @@
<?php
return array(
'grabber' => array(
'%/comic.*%' => array(
'test_url' => 'http://www.loadingartist.com/comic/lifted-spirits/',
'body' => array('//div[@class="comic"]'),
'strip' => array(),
'filter' => array(
'%.*%' => array(
'%-\\d+x\\d+%' => "",
)
)
);
);

View File

@ -0,0 +1,15 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'body' => array('//div[@id="comic"]//img'),
'strip' => array(),
'test_url' => 'http://www.lukesurl.com/archives/comic/665-3-of-clubs',
)
),
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,12 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'test_url' => 'http://www.marriedtothesea.com/index.php?date=052915',
'body' => array(
'//div[@align]/a/img',
),
'strip' => array(),
)
)
);

View File

@ -0,0 +1,13 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'body' => array(
'//img[@id="cc-comic"]',
'//div[@class="cc-newsbody"]'
),
'strip' => array(),
'test_url' => 'http://www.marycagle.com/letsspeakenglish/74-grim-reality/',
)
),
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%alt="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,19 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'body' => array(
'//img[@id="strip"]',
'//a/div[@id="nx"]/..'
),
'strip' => array(),
'test_url' => 'http://oglaf.com/slodging/'
)
),
'filter' => array(
'%.*%' => array(
'%alt="(.+)" title="(.+)" */>%' => "/><br/>$1<br/>$2<br/>",
'%</a>%' => 'Next page</a>',
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,9 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
// the extra space is required to strip the title cleanly
'%title="(.+) " */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,12 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'test_url' => 'http://www.pixelbeat.org/programming/sigpipe_handling.html#1425573246',
'body' => array(
'//div[@class="contentText"]',
),
'strip' => array(),
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%(<img.+/s/[^"]+/)(.+)%' => '$1$2$1bonus.png"/>'
)
)
);

View File

@ -4,7 +4,8 @@ return array(
'%.*%' => array(
'test_url' => 'http://satwcomic.com/day-at-the-beach',
'body' => array(
'//div[@class="container"]/center/a/img'
'//div[@class="container"]/center/a/img',
'//span[@itemprop="articleBody"]'
),
'strip' => array(),
)

View File

@ -0,0 +1,18 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'body' => array(
'//div[@class="comicpane"]/a/img',
'//div[@class="entry"]'
),
'strip' => array(),
'test_url' => 'http://sentfromthemoon.com/archives/1417'
)
),
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%(<img.+)(\.png"/>)%' => '$1$2$1after$2'
)
)
);

View File

@ -0,0 +1,13 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'test_url' => 'http://stupidfox.net/134-sleepy-time',
'body' => array(
'//div[@class="comicmid"]/center/a/img',
'//div[@class="stand_high"]'
),
'strip' => array(),
)
)
);

View File

@ -0,0 +1,15 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'test_url' => 'http://www.subtraction.com/2015/06/06/time-lapse-video-of-one-world-trade-center/',
'body' => array('//article/div[@class="entry-content"]'),
'strip' => array()
)
),
'filter' => array(
'%.*%' => array(
'%\+<h3.*/ul>%' => '',
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%-\\d+x\\d+%' => "",
)
)
);

View File

@ -0,0 +1,18 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'body' => array(
'//div[@class="comicpane"]/a/img',
'//div[@class="entry"]'
),
'strip' => array(),
'test_url' => 'http://sentfromthemoon.com/archives/1417'
)
),
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,8 @@
<?php
return array(
'filter' => array(
'%.*%' => array(
'%title="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -0,0 +1,10 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'body' => array('//div[@class="entry-content"]'),
'strip' => array(),
'test_url' => 'http://voz.vn/2015/06/06/muon-trai-nghiem-bphone-hay-den-fpt-shop/',
)
),
);

View File

@ -1,17 +0,0 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'test_url' => array(
'http://www.lemonde.fr/societe/article/2013/08/30/boris-boillon-ancien-ambassadeur-de-sarkozy-arrete-avec-350-000-euros-en-liquide_3469109_3224.html',
'http://www.lemonde.fr/afrique/article/2015/04/06/plonge-dans-la-crise-l-angola-revele-son-vrai-visage_4610364_3212.html',
),
'body' => array(
'//div[@id="articleBody"]',
'//div[@itemprop="articleBody"]',
),
'strip' => array(
),
)
)
);

View File

@ -1,13 +0,0 @@
<?php
return array(
'grabber' => array(
'%.*%' => array(
'test_url' => 'http://www.pcinpact.com/news/85954-air-france-ne-vous-demande-plus-deteindre-vos-appareils-electroniques.htm?utm_source=PCi_RSS_Feed&utm_medium=news&utm_campaign=pcinpact',
'body' => array(
'//div[contains(@id, "actu_content")]',
),
'strip' => array(
),
)
)
);

View File

@ -0,0 +1,41 @@
<?php
return array(
'grabber' => array(
'%^/zeit-magazin.*%' => array(
'test_url' => 'http://www.zeit.de/zeit-magazin/2015/15/pegida-kathrin-oertel-lutz-bachmann',
'body' => array(
'//article[@class="article"]'
),
'strip' => array(
'//header/div/h1',
'//header/div/div[@class="article__head__subtitle"]',
'//header/div/div[@class="article__column__author"]',
'//header/div/div[@class="article__column__author"]',
'//header/div/span[@class="article__head__meta-wrap"]',
'//form',
'//style',
'//div[contains(@class, "ad-tile")]',
'//div[@class="iqd-mobile-adplace"]',
'//div[@id="iq-artikelanker"]',
'//div[@id="js-social-services"]',
'//section[@id="js-comments"]',
'//aside'
)
),
'%.*%' => array(
'test_url' => 'http://www.zeit.de/politik/ausland/2015-04/thessaloniki-krise-griechenland-yannis-boutaris/',
'body' => array(
'//div[@class="article-body"]'
),
'strip' => array(
'//*[@class="articleheader"]',
'//*[@class="excerpt"]',
'//div[contains(@class, "ad")]',
'//div[@itemprop="video"]',
'//*[@class="articlemeta"]',
'//*[@class="articlemeta-clear"]',
'//*[@class="zol_inarticletools"]'
)
)
)
);

View File

@ -1,13 +1,8 @@
<?php
return array(
'grabber' => array(
'filter' => array(
'%.*%' => array(
'test_url' => 'http://xkcd.com/1472/',
'body' => array(
'//div[@id="comic"]',
),
'strip' => array(
),
'%alt="(.+)" */>%' => "/><br/>$1"
)
)
);

View File

@ -4,6 +4,7 @@ namespace PicoFeed\Filter;
use PHPUnit_Framework_TestCase;
use PicoFeed\Client\Url;
use PicoFeed\Config\Config;
class AttributeFilterTest extends PHPUnit_Framework_TestCase
@ -190,4 +191,134 @@ class AttributeFilterTest extends PHPUnit_Framework_TestCase
$this->assertEquals('title="&quot;a&quot;"', $filter->toHtml(array('title' => '"a"')));
$this->assertEquals('title="ç" alt="b"', $filter->toHtml(array('title' => 'ç', 'alt' => 'b')));
}
public function testNoImageProxySet()
{
$f = Filter::html('<p>Image <img src="/image.png" alt="My Image"/></p>', 'http://foo');
$this->assertEquals(
'<p>Image <img src="http://foo/image.png" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyWithHTTPLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('http://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyWithHTTPSLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$f = Filter::html('<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('https://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToUnknownProtocol()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('tripleX');
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToHTTPwithHTTPLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('http');
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('http://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToHTTPwithHTTPSLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('http');
$f = Filter::html('<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToHTTPSwithHTTPLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('https');
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToHTTPSwithHTTPSLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('https');
$f = Filter::html('<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('https://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testsetFilterImageProxyCallback()
{
$config = new Config;
$config->setFilterImageProxyCallback(function ($image_url) {
$key = hash_hmac('sha1', $image_url, 'secret');
return 'https://mypublicproxy/'.$key.'/'.rawurlencode($image_url);
});
$f = Filter::html('<p>Image <img src="/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="https://mypublicproxy/4924964043f3119b3cf2b07b1922d491bcc20092/'.rawurlencode('http://foo/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
}

View File

@ -91,133 +91,35 @@ class FilterTest extends PHPUnit_Framework_TestCase
$this->assertEquals('<p>Testboo</p>', $f->execute());
}
public function testNoImageProxySet()
public function testNormalizeData()
{
$f = Filter::html('<p>Image <img src="/image.png" alt="My Image"/></p>', 'http://foo');
// invalid data link escape control character
$this->assertEquals('<xml>random text</xml>', Filter::normalizeData("<xml>random\x10 text</xml>"));
$this->assertEquals('<xml>random text</xml>', Filter::normalizeData("<xml>random&#x10; text</xml>"));
$this->assertEquals('<xml>random text</xml>', Filter::normalizeData("<xml>random&#16; text</xml>"));
$this->assertEquals(
'<p>Image <img src="http://foo/image.png" alt="My Image"/></p>',
$f->execute()
);
}
// invalid unit seperator control character (lower and upper case)
$this->assertEquals('<xml>random text</xml>', Filter::normalizeData("<xml>random\x1f text</xml>"));
$this->assertEquals('<xml>random text</xml>', Filter::normalizeData("<xml>random\x1F text</xml>"));
$this->assertEquals('<xml>random text</xml>', Filter::normalizeData("<xml>random&#x1f; text</xml>"));
$this->assertEquals('<xml>random text</xml>', Filter::normalizeData("<xml>random&#x1F; text</xml>"));
$this->assertEquals('<xml>random text</xml>', Filter::normalizeData("<xml>random&#31; text</xml>"));
public function testImageProxyWithHTTPLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
/*
* Do not test invalid multibyte characters. The output depends on php
* version and character.
*
* php 5.3: always null
* php >5.3: sometime null, sometimes the stripped string
*/
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
// invalid backspace control character + valid multibyte character
$this->assertEquals('<xml>“random“ text</xml>', Filter::normalizeData("<xml>\xe2\x80\x9crandom\xe2\x80\x9c\x08 text</xml>"));
$this->assertEquals('<xml>&#x201C;random&#x201C; text</xml>', Filter::normalizeData("<xml>&#x201C;random&#x201C;&#x08; text</xml>"));
$this->assertEquals('<xml>&#8220;random&#8220; text</xml>', Filter::normalizeData("<xml>&#8220;random&#8220;&#08; text</xml>"));
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('http://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyWithHTTPSLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$f = Filter::html('<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('https://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToUnknownProtocol()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('tripleX');
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToHTTPwithHTTPLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('http');
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('http://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToHTTPwithHTTPSLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('http');
$f = Filter::html('<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToHTTPSwithHTTPLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('https');
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToHTTPSwithHTTPSLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('https');
$f = Filter::html('<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('https://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testsetFilterImageProxyCallback()
{
$config = new Config;
$config->setFilterImageProxyCallback(function ($image_url) {
$key = hash_hmac('sha1', $image_url, 'secret');
return 'https://mypublicproxy/'.$key.'/'.rawurlencode($image_url);
});
$f = Filter::html('<p>Image <img src="/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="https://mypublicproxy/4924964043f3119b3cf2b07b1922d491bcc20092/'.rawurlencode('http://foo/image.png').'" alt="My Image"/></p>',
$f->execute()
);
// do not convert valid entities to utf-8 character
$this->assertEquals('<xml attribute="&#34;value&#34;">random text</xml>', Filter::normalizeData('<xml attribute="&#34;value&#34;">random text</xml>'));
$this->assertEquals('<xml attribute="&#x22;value&#x22;">random text</xml>', Filter::normalizeData('<xml attribute="&#x22;value&#x22;">random text</xml>'));
}
}

View File

@ -33,4 +33,15 @@ class ParserTest extends PHPUnit_Framework_TestCase
$this->assertEquals('Blandine Grosjean', XmlParser::getNamespaceValue($xml->channel->item[0], $namespaces, 'creator'));
$this->assertEquals('Pierre-Carl Langlais', XmlParser::getNamespaceValue($xml->channel->item[1], $namespaces, 'creator'));
}
public function testFeedsWithInvalidCharacters()
{
$parser = new Rss20(file_get_contents('tests/fixtures/lincoln_loop.xml'));
$feed = $parser->execute();
$this->assertNotEmpty($feed->items);
$parser = new Rss20(file_get_contents('tests/fixtures/next_inpact_full.xml'));
$feed = $parser->execute();
$this->assertNotEmpty($feed->items);
}
}

View File

@ -257,14 +257,6 @@ class Rss20ParserTest extends PHPUnit_Framework_TestCase
$this->assertNotEmpty($feed->items);
$this->assertEquals('http://geekstammtisch.de/#GST001', $feed->items[1]->getUrl());
$parser = new Rss20(file_get_contents('tests/fixtures/lincoln_loop.xml'));
$feed = $parser->execute();
$this->assertNotEmpty($feed->items);
$parser = new Rss20(file_get_contents('tests/fixtures/next_inpact_full.xml'));
$feed = $parser->execute();
$this->assertNotEmpty($feed->items);
$parser = new Rss20(file_get_contents('tests/fixtures/jeux-linux.fr.xml'));
$feed = $parser->execute();
$this->assertNotEmpty($feed->items);

View File

@ -84,7 +84,7 @@ class FaviconTest extends PHPUnit_Framework_TestCase
{
$favicon = new Favicon;
$this->assertTrue($favicon->exists('https://en.wikipedia.org/favicon.ico'));
$this->assertTrue($favicon->exists('https://miniflux.net/favicon.ico'));
$this->assertFalse($favicon->exists('http://minicoders.com/favicon.ico'));
$this->assertFalse($favicon->exists('http://blabla'));
$this->assertFalse($favicon->exists(''));

View File

@ -231,7 +231,7 @@ class ReaderTest extends PHPUnit_Framework_TestCase
$reader = new Reader;
$client = $reader->discover('http://planete-jquery.fr');
$this->assertInstanceOf('PicoFeed\Parser\Rss20', $reader->getParser($client->getUrl(), $client->getContent(), $client->getEncoding()));
$this->assertInstanceOf('PicoFeed\Parser\Rss10', $reader->getParser($client->getUrl(), $client->getContent(), $client->getEncoding()));
$reader = new Reader;
$client = $reader->discover('http://cabinporn.com/');