Initial commit

This commit is contained in:
Michael Reber 2019-11-12 16:59:19 +01:00
parent 0a00b0b27f
commit 56f53d15ff
69 changed files with 7157 additions and 0 deletions

57
config.php Normal file
View File

@ -0,0 +1,57 @@
<?php
// all possible options will be stored
$config = array();
$config['website-name'] = "Web Proxy";
$config['access-password'] = "MY_PASSWORD_TO_ACCESS";
$config['website-url'] = "MY_FQDN/webproxy";
$config['website-description'] = "Web Proxy";
// a unique key that identifies this application - DO NOT LEAVE THIS EMPTY!
$config['app_key'] = 'ADD_HERE_SOME_RANDOM_STRING';
// a secret key to be used during encryption
$config['encryption_key'] = '';
/*
how unique is each URL that is generated by this proxy app?
0 - no encoding of any sort. People can link to proxy pages directly: ?q=http://www.yahoo.com
1 - Base64 encoding only, people can hotlink to your proxy
2 - unique to the IP address that generated it. A person that generated that URL, can bookmark it and visit it and any point
3 - unique to that session and IP address - URL no longer valid anywhere when that browser session that generated it ends
*/
$config['url_mode'] = 2;
// plugins to load - plugins will be loaded in this exact order as in array
$config['plugins'] = array(
'HeaderRewrite',
'Stream',
// ^^ do not disable any of the plugins above
'Cookie',
'Proxify',
'UrlForm',
// site specific plugins below
'Youtube',
'DailyMotion',
'Twitter'
);
// additional curl options to go with each request
$config['curl'] = array(
// CURLOPT_PROXY => '',
// CURLOPT_CONNECTTIMEOUT => 5
);
//$config['replace_title'] = 'Google Search';
//$config['error_redirect'] = "https://unblockvideos.com/#error={error_msg}";
//$config['index_redirect'] = 'https://unblockvideos.com/';
// $config['replace_icon'] = 'icon_url';
// this better be here other Config::load fails
return $config;
?>

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

145
index.php Normal file
View File

@ -0,0 +1,145 @@
<?php
require(__DIR__.'/login.php');
define('PROXY_START', microtime(true));
require("vendor/autoload.php");
use Proxy\Http\Request;
use Proxy\Http\Response;
use Proxy\Plugin\AbstractPlugin;
use Proxy\Event\FilterEvent;
use Proxy\Config;
use Proxy\Proxy;
// start the session
session_start();
// load config...
Config::load('./config.php');
// custom config file to be written to by a bash script or something
Config::load('./custom_config.php');
if (!Config::get('app_key')) {
die("app_key inside config.php cannot be empty!");
}
if (!function_exists('curl_version')) {
die("cURL extension is not loaded!");
}
// how are our URLs be generated from this point? this must be set here so the proxify_url function below can make use of it
if (Config::get('url_mode') == 2) {
Config::set('encryption_key', md5(Config::get('app_key') . $_SERVER['REMOTE_ADDR']));
} else if (Config::get('url_mode') == 3) {
Config::set('encryption_key', md5(Config::get('app_key') . session_id()));
}
// very important!!! otherwise requests are queued while waiting for session file to be unlocked
session_write_close();
// form submit in progress...
if (isset($_POST['url'])) {
$url = $_POST['url'];
if (strpos ($url, '.') !== false){
$url = add_http($url);
header("HTTP/1.1 302 Found");
header('Location: '.proxify_url($url));
exit;
}
else {
$url = 'http://www.google.com/search?q=' . urlencode($url);
$url = add_http($url);
header("HTTP/1.1 302 Found");
header('Location: '.proxify_url($url));
exit;
}
} else if (!isset($_GET['q'])) {
// must be at homepage - should we redirect somewhere else?
if (Config::get('index_redirect')) {
// redirect to...
header("HTTP/1.1 302 Found");
header("Location: " . Config::get('index_redirect'));
} else {
if (isset($_GET["tos"]) != "") {
echo render_template("./templates/tos.php", array(
'version' => Proxy::VERSION
));
} else {
echo render_template("./templates/main.php", array('version' => Proxy::VERSION));
}
}
exit;
}
// decode q parameter to get the real URL
$url = url_decrypt($_GET['q']);
$proxy = new Proxy();
// load plugins
foreach (Config::get('plugins', array()) as $plugin) {
$plugin_class = $plugin . 'Plugin';
if (file_exists('./plugins/' . $plugin_class . '.php')) {
// use user plugin from /plugins/
require_once('./plugins/' . $plugin_class . '.php');
} else if (class_exists('\\Proxy\\Plugin\\' . $plugin_class)) {
// does the native plugin from php-proxy package with such name exist?
$plugin_class = '\\Proxy\\Plugin\\' . $plugin_class;
}
// otherwise plugin_class better be loaded already through composer.json and match namespace exactly \\Vendor\\Plugin\\SuperPlugin
$proxy->getEventDispatcher()->addSubscriber(new $plugin_class());
}
try {
// request sent to index.php
$request = Request::createFromGlobals();
// remove all GET parameters such as ?q=
$request->get->clear();
// forward it to some other URL
$response = $proxy->forward($request, $url);
// if that was a streaming response, then everything was already sent and script will be killed before it even reaches this line
$response->send();
} catch (Exception $ex) {
// if the site is on server2.proxy.com then you may wish to redirect it back to proxy.com
if (Config::get("error_redirect")) {
$url = render_string(Config::get("error_redirect"), array(
'error_msg' => rawurlencode($ex->getMessage())
));
// Cannot modify header information - headers already sent
header("HTTP/1.1 302 Found");
header("Location: {$url}");
} else {
echo render_template("./templates/main.php", array(
'url' => $url,
'error_msg' => $ex->getMessage(),
'version' => Proxy::VERSION
));
}
}
?>

9
language.php Normal file
View File

@ -0,0 +1,9 @@
<?php
$lang["slogan"] = "remove the borders";
$lang["wikipedia"] = "Wikipedia";
$lang["back"] = "Back";
$lang["go"] = "Go";
$lang["home"] = "Home";
$lang["tos"] = "This is an private Web Proxy - Unauthorized use is strictly prohibited!";
$lang["agree"] = "michu-IT";
$lang["tos_2"] = "terms of use";

429
login.php Normal file

File diff suppressed because one or more lines are too long

25
plugins/TestPlugin.php Normal file
View File

@ -0,0 +1,25 @@
<?php
use Proxy\Plugin\AbstractPlugin;
use Proxy\Event\ProxyEvent;
class TestPlugin extends AbstractPlugin {
public function onBeforeRequest(ProxyEvent $event){
// fired right before a request is being sent to a proxy
}
public function onHeadersReceived(ProxyEvent $event){
// fired right after response headers have been fully received - last chance to modify before sending it back to the user
}
public function onCurlWrite(ProxyEvent $event){
// fired as the data is being written piece by piece
}
public function onCompleted(ProxyEvent $event){
// fired after the full response=headers+body has been read - will only be called on "non-streaming" responses
}
}
?>

39
plugins/UrlFormPlugin.php Normal file
View File

@ -0,0 +1,39 @@
<?php
use Proxy\Plugin\AbstractPlugin;
use Proxy\Event\ProxyEvent;
class UrlFormPlugin extends AbstractPlugin {
public function onCompleted(ProxyEvent $event){
$request = $event['request'];
$response = $event['response'];
$url = $request->getUri();
// we attach url_form only if this is a html response
if(!is_html($response->headers->get('content-type'))){
return;
}
// this path would be relative to index.php that included it?
$url_form = render_template("./templates/url_form.php", array(
'url' => $url
));
$output = $response->getContent();
// does the html page contain <body> tag, if so insert our form right after <body> tag starts
$output = preg_replace('@<body.*?>@is', '$0'.PHP_EOL.$url_form, $output, 1, $count);
// <body> tag was not found, just put the form at the top of the page
if($count == 0){
$output = $url_form.$output;
}
$response->setContent($output);
}
}
?>

84
templates/main.php Normal file
View File

@ -0,0 +1,84 @@
<?php
require(__DIR__ . "/../language.php");
require(__DIR__ . "/../config.php");
?>
<!DOCTYPE html>
<html lang="en">
<head>
<title><?php echo $config['website-name']; ?></title>
<meta charset="utf-8">
<meta name="description" content="<?php echo $config["website-description"]; ?>">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel='shortcut icon' type='image/x-icon' href='favicon.ico' />
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css">
<style type="text/css">
body {
background-color: #373737 !important;
background-image: url(https://www.blackgate.org/wood.jpg);
}
.jumbotron {
background-color: #e9ecefc9 !important;
}
</style>
</head>
<body>
<div class="container text-center" style="margin-top:7%;">
<div class="row">
<div class="col-md-12">
<?php if (isset($error_msg)) { ?>
<div class="alert alert-danger" role="alert">
<p><?php echo $error_msg; ?></p>
</div>
<?php } ?>
<div class="jumbotron">
<h1 class="display-3"><?php echo $config['website-name']; ?></h1>
<p class="lead"><?php echo $lang["slogan"]; ?></p>
<div class="btn-group" role="group">
<a id="twitter" class="btn btn-info" href="#">Twitter</a>
<a id="youtube" class="btn btn-danger" href="#">YouTube</a>
<a id="facebook" class="btn btn-primary" href="#">Facebook</a>
</div>
<br>
<br>
<div class="btn-group" role="group">
<a id="vikipedi" class="btn btn-secondary" href="#"><?php echo $lang["wikipedia"]; ?></a>
<a id="google" class="btn btn-success" href="#">Google</a>
</div>
<p class="lead">
<form class="form-group" action="index.php" method="post">
<input id="url" name="url" type="text" class="form-control" autocomplete="on" placeholder="URL or Search"
autofocus required/>
<br>
<input class="btn btn-primary btn-lg" type="submit" value="<?php echo $lang["go"]; ?>"/>
</form>
</div>
<div class="text-center">
<p style="font-size:15px; color:white">
<small><?php echo $lang["agree"]; ?> <a
href="<?php echo $config['website-url']; ?>/?tos"><?php echo $lang["tos_2"]; ?></a>
</small>
</p>
</div>
</div>
</div>
<script src="//code.jquery.com/jquery-1.9.1.js"></script>
<script>
$('#twitter').click(function () {
$('#url').val('https://twitter.com');
});
$('#youtube').click(function () {
$('#url').val('https://youtube.com');
});
$('#facebook').click(function () {
$('#url').val('https://facebook.com');
});
$('#google').click(function () {
$('#url').val('https://google.com');
});
$('#vikipedi').click(function () {
$('#url').val('https://wikipedia.org');
});
</script>
</div>
</body>
</html>

BIN
templates/proxy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

30
templates/tos.php Normal file
View File

@ -0,0 +1,30 @@
<?php
require(__DIR__ . "/../language.php");
require(__DIR__ . "/../config.php");
?>
<!DOCTYPE html>
<html lang="en">
<head>
<title><?php echo $config['website-name']; ?></title>
<meta charset="utf-8">
<meta name="description" content="<?php echo $config["website-description"]; ?>">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel='shortcut icon' type='image/x-icon' href='favicon.ico' />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css">
</head>
<body>
<div class="container text-center" style="margin-top:5%;">
<div class="row">
<div class="col-md-12">
<div class="jumbotron">
<h1 class="display-3"><?php echo $config['website-name']; ?></h1>
<p class="lead"><?php echo $lang["slogan"]; ?></p>
<p><?php echo $lang["tos"]; ?></p>
<a href="<?php echo $config['website-url']; ?>"
class="btn btn-primary btn-lg"><?php echo $lang["back"]; ?></a>
</div>
</div>
</div>
</div>
</body>
</html>

80
templates/url_form.php Normal file
View File

@ -0,0 +1,80 @@
<?php
require(__DIR__ . "/../language.php");
?>
<style type="text/css">
html body {
margin-top: 50px !important;
position: relative;
}
#top_form {
position: fixed;
top: 0;
left: 0;
width: 100%;
margin: 0;
z-index: 2100000000;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
border-bottom: 1px solid #151515;
background: #CCCCCC;
height: 45px;
line-height: 45px;
}
#top_form input[name=url] {
width: 550px;
height: 20px;
padding: 5px;
font: 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
border: 0px none;
background: none repeat scroll 0% 0% #FFF;
}
</style>
<script>
var url_text_selected = false;
function smart_select(ele) {
ele.onblur = function () {
url_text_selected = false;
};
ele.onclick = function () {
if (url_text_selected == false) {
this.focus();
this.select();
url_text_selected = true;
}
};
}
</script>
<div id="top_form">
<div style="width:800px; margin:0 auto;">
<form method="post" action="index.php" target="_top" style="margin:0; padding:0;">
<input type="button" value="<?php echo $lang["home"]; ?>" onclick=" window.location.href='index.php'">
<input type="text" name="url" value="<?php echo $url; ?>" autocomplete="off">
<input type="hidden" name="form" value="1">
<input type="submit" value="<?php echo $lang["go"]; ?>">
</form>
</div>
</div>
<script type=" text/javascript">
smart_select(document.getElementsByName("url")[0]);
</script>

@ -0,0 +1 @@
Subproject commit 172202c9b7913dc397b0a2eeacb68984a73f5c6e

View File

@ -0,0 +1,3 @@
/vendor/*
composer.lock
.htaccess

21
vendor/athlon1600/php-proxy/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016
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.

99
vendor/athlon1600/php-proxy/README.md vendored Normal file
View File

@ -0,0 +1,99 @@
php-proxy
=========
Proxy script built on PHP, Symfony and cURL.
This library borrows ideas from Glype, Jenssegers proxy, and Guzzle.
PHP-Proxy Web Application
-------
If you're looking for a **project** version of this script that functions as a Web Application similar to Glype, then visit
[**php-proxy-app**](https://github.com/Athlon1600/php-proxy-app)
See this php-proxy in action:
<a href="https://unblockvideos.com/" target="_blank">UnblockVideos.com</a>
Installation
-------
Install it using [Composer](http://getcomposer.org):
```bash
composer require athlon1600/php-proxy
```
Example
--------
```php
require('vendor/autoload.php');
use Proxy\Http\Request;
use Proxy\Proxy;
$request = Request::createFromGlobals();
$proxy = new Proxy();
$proxy->getEventDispatcher()->addListener('request.before_send', function($event){
$event['request']->headers->set('X-Forwarded-For', 'php-proxy');
});
$proxy->getEventDispatcher()->addListener('request.sent', function($event){
if($event['response']->getStatusCode() != 200){
die("Bad status code!");
}
});
$proxy->getEventDispatcher()->addListener('request.complete', function($event){
$content = $event['response']->getContent();
$content .= '<!-- via php-proxy -->';
$event['response']->setContent($content);
});
$response = $proxy->forward($request, "https://www.yahoo.com");
// send the response back to the client
$response->send();
```
Plugin Example
--------
```php
namespace Proxy\Plugin;
use Proxy\Plugin\AbstractPlugin;
use Proxy\Event\ProxyEvent;
use Proxy\Html;
class MultiSiteMatchPlugin extends AbstractPlugin {
// Matches multiple domain names (abc.com, abc.de, abc.pl) using regex (you MUST use / character)
protected $url_pattern = '/^abc\.(com|de|pl)$/is';
// Matches a single domain name
//protected $url_pattern = 'abc.com';
public function onCompleted(ProxyEvent $event){
$response = $event['response'];
$html = $response->getContent();
// do your stuff here...
$response->setContent($html);
}
}
```
Notice that you must use the **/** character for regexes on ```$url_pattern```

View File

@ -0,0 +1,21 @@
{
"name": "athlon1600/php-proxy",
"type": "library",
"keywords": ["php proxy", "proxy script", "php web proxy", "web proxy", "php proxy script"],
"homepage": "https://www.php-proxy.com/",
"require": {
"ext-curl": "*",
"symfony/event-dispatcher": "~3.2"
},
"suggest": {
"predis/predis": "For caching purposes"
},
"autoload": {
"psr-4": {
"Proxy\\": "src/"
},
"files": [
"src/helpers.php"
]
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Proxy;
// based off of this:
// http://v3.golaravel.com/api/source-class-Laravel.Config.html#3-235
class Config {
private static $config = array();
public static function get($key, $default = null){
return self::has($key) ? static::$config[$key] : $default;
}
public static function set($key, $value){
self::$config[$key] = $value;
}
public static function has($key){
return isset(static::$config[$key]);
}
public static function load($path){
if(file_exists($path)){
// Successful includes, unless overridden by the included file, return 1.
$data = require($path);
if(is_array($data)){
self::$config = array_merge(self::$config, $data);
}
}
}
}
?>

View File

@ -0,0 +1,39 @@
<?php
namespace Proxy\Event;
use Symfony\Component\EventDispatcher\Event;
// http://symfony.com/doc/current/components/event_dispatcher/generic_event.html
class ProxyEvent extends Event implements \ArrayAccess {
private $data;
public function __construct($data = array()){
$this->data = $data;
}
public function offsetSet($offset, $value){
if(is_null($offset)) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}
public function offsetExists($offset){
return isset($this->data[$offset]);
}
public function offsetUnset($offset){
unset($this->data[$offset]);
}
public function offsetGet($offset){
return isset($this->data[$offset]) ? $this->data[$offset] : null;
}
}
?>

182
vendor/athlon1600/php-proxy/src/Html.php vendored Normal file
View File

@ -0,0 +1,182 @@
<?php
namespace Proxy;
class Html {
public static function remove_scripts($html){
$html = preg_replace('/<\s*script[^>]*>(.*?)<\s*\/\s*script\s*>/is', '', $html);
return $html;
}
public static function remove_styles($html){
$html = preg_replace('/<\s*style[^>]*>(.*?)<\s*\/\s*style\s*>/is', '', $html);
return $html;
}
public static function remove_comments($html){
return preg_replace('/<!--(.*?)-->/s', '', $html);
}
private static function find($selector, $html, $start_from = 0){
$html = substr($html, $start_from);
$inner_start = 0;
$inner_end = 0;
$pattern = '//';
if(substr($selector, 0, 1) == '#'){
$pattern = '/<(\w+)[^>]+id="'.substr($selector, 1).'"[^>]*>/is';
} else if(substr($selector, 0, 1) == '.'){
$pattern = '/<(\w+)[^>]+class="'.substr($selector, 1).'"[^>]*>/is';
} else {
return false;
}
if(preg_match($pattern, $html, $matches, PREG_OFFSET_CAPTURE)){
$outer_start = $matches[0][1];
$inner_start = $matches[0][1] + strlen($matches[0][0]);
// tag stuff
$tag_name = $matches[1][0];
$tag_len = strlen($tag_name);
$run_count = 300;
// "open" <tag elements we found so far
$open_count = 1;
$start = $inner_start;
while($open_count != 0 && $run_count-- > 0){
$open_tag = strpos($html, "<{$tag_name}", $start);
$close_tag = strpos($html, "</{$tag_name}", $start);
// nothing was found?
if($open_tag === false && $close_tag === false){
break;
}
//echo "open_tag: {$open_tag}, close_tag {$close_tag}\r\n";
// found OPEN tag
if($close_tag === false || ($open_tag !== false && $open_tag < $close_tag) ){
$open_count++;
$start = $open_tag + $tag_len + 1;
//echo "found open tag: ".substr($html, $open_tag, 20)." at {$open_tag} \r\n";
// found CLOSE tag
} else if($open_tag === false || ($close_tag !== false && $close_tag < $open_tag) ){
$open_count--;
$start = $close_tag + $tag_len + 2;
//echo "found close tag: ".substr($html, $close_tag, 20)." at {$close_tag} \r\n";
}
}
// something went wrong... don't bother returning anything
if($open_count != 0){
return false;
}
$outer_end = $close_tag + $tag_len + 3;
$inner_end = $close_tag;
return array(
'outer_start' => $outer_start + $start_from,
'inner_start' => $inner_start + $start_from,
'inner_end' => $inner_end + $start_from,
'outer_end' => $outer_end + $start_from
);
}
return false;
}
public static function extract_inner($selector, $html){
return self::extract($selector, $html, true);
}
public static function extract_outer($selector, $html){
return self::extract($selector, $html, false);
}
private static function extract($selector, $html, $inner = false){
$pos = 0;
$limit = 300;
$result = array();
$data = false;
do {
$data = self::find($selector, $html, $pos);
if($data){
$code = substr($html, $inner ? $data['inner_start'] : $data['outer_start'],
$inner ? $data['inner_end'] - $data['inner_start'] : $data['outer_end'] - $data['outer_start']);
$result[] = $code;
$pos = $data['outer_end'];
}
} while ($data && --$limit > 0);
return $result;
}
public static function remove($selector, $html){
return self::replace($selector, '', $html, false);
}
public static function replace_outer($selector, $replace, $html, &$matches = NULL){
return self::replace($selector, $replace, $html, false, $matches);
}
public static function replace_inner($selector, $replace, $html, &$matches = NULL){
return self::replace($selector, $replace, $html, true, $matches);
}
private static function replace($selector, $replace, $html, $replace_inner = false, &$matches = NULL){
$start_from = 0;
$limit = 300;
$data = false;
$replace = (array)$replace;
do {
$data = self::find($selector, $html, $start_from);
if($data){
$r = array_shift($replace);
// from where to where will we be replacing?
$replace_space = $replace_inner ? $data['inner_end'] - $data['inner_start'] : $data['outer_end'] - $data['outer_start'];
$replace_len = strlen($r);
if($matches !== NULL){
$matches[] = substr($html, $replace_inner ? $data['inner_start'] : $data['outer_start'], $replace_space);
}
$html = substr_replace($html, $r, $replace_inner ? $data['inner_start'] : $data['outer_start'], $replace_space);
// next time we resume search at position right at the end of this element
$start_from = $data['outer_end'] + ($replace_len - $replace_space);
}
} while ($data && --$limit > 0);
return $html;
}
}
?>

View File

@ -0,0 +1,84 @@
<?php
namespace Proxy\Http;
/*
heavily borrowed from Symfony's ParameterBag and Guzzle Collection
https://github.com/guzzle/guzzle/blob/v3.5.0/src/Guzzle/Common/Collection.php
*/
class ParamStore {
protected $data = array();
protected $case_sensitive;
public function __construct($parameters = array(), $case_sensitive = false){
$this->data = $parameters;
$this->case_sensitive = $case_sensitive;
}
private function normalizeKey($key){
return $this->case_sensitive ? $key : strtolower($key);
}
public function set($key, $value, $replace = true){
$key = $this->normalizeKey($key);
// replacing or does not have existing key filled yet
if($replace || !$this->has($key)){
$this->data[$key] = $value;
} else {
if(is_array($this->data[$key])){
$this->data[$key][] = $value;
} else {
$this->data[$key] = array($this->data[$key], $value);
}
}
}
public function replace(array $data){
// remove all existing items first
$this->clear();
foreach($data as $key => $value){
$this->set($key, $value);
}
}
public function remove($key){
unset($this->data[$this->normalizeKey($key)]);
}
public function clear(){
$this->data = array();
}
public function has($key){
return isset($this->data[$this->normalizeKey($key)]);
}
public function get($key, $default = null){
$key = $this->normalizeKey($key);
return $this->has($key) ? $this->data[$key] : $default;
}
// Returns an array of all values currently stored
public function all(){
return $this->data;
}
public function __toString(){
return json_encode($this->data, true);
}
}
?>

View File

@ -0,0 +1,362 @@
<?php
namespace Proxy\Http;
use Proxy\Http\ParamStore;
class Request {
private $method;
private $url;
private $protocol_version = '1.1';
// Custom attributes to go with each Request instance - has nothing to do with HTTP
public $params; // parameters
// HTTP headers for this request - all in lowercase
public $headers;
// Here we store cookies for that request
//public $cookies;
// Collection of POST fields to be submitted
public $post;
// Query string parameters for URL
public $get;
// Files to be uploaded with POST
public $files;
// User set body contents
private $body = null;
// Library generated body that is regenerated through prepare method
private $prepared_body = null;
public function __construct($method, $url, $headers = array(), $body = null){
$this->params = new ParamStore();
$this->headers = new ParamStore();
// http params
$this->post = new ParamStore(null, true);
$this->get = new ParamStore(null, true);
$this->files = new ParamStore(null, true);
$this->setMethod($method);
$this->setUrl($url);
$this->setBody($body);
// make the request ready to be sent right from the start - prepare must be called manually from this point on if you ever add post or file parameters
$this->prepare();
}
/*
Does multiple things
- regenerate content body based on $post and $files parameters
- set content-type, content-length headers
- set transfer-encoding, expect headers
*/
public function prepare(){
/*
Any HTTP/1.1 message containing an entity-body SHOULD include a Content-Type header field defining the media type of that body.
http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
*/
// Must be a multipart request
if($this->files->all()){
$boundary = self::generateBoundary();
$this->prepared_body = Request::buildPostBody($this->post->all(), $this->files->all(), $boundary);
$this->headers->set('content-type', 'multipart/form-data; boundary='.$boundary);
} else if($this->post->all()){
$this->prepared_body = http_build_query($this->post->all());
$this->headers->set('content-type', 'application/x-www-form-urlencoded');
} else {
$this->headers->set('content-type', $this->detectContentType($this->body));
$this->prepared_body = $this->body;
}
/*
The transfer-length of a message is the length of the message-body as it appears in the message; that is, after any transfer-codings have been applied.
http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
*/
$len = strlen($this->prepared_body);
if($len > 0){
$this->headers->set('content-length', $len);
} else {
$this->headers->remove('content-length');
$this->headers->remove('content-type');
}
}
public function __toString(){
$str = $this->getMethod().' '.$this->getUrl().' HTTP/'.$this->getProtocolVersion()."\r\n";
return $str.$this->getRawHeaders()."\r\n\r\n".$this->getRawBody();
}
public function setMethod($method){
$this->method = strtoupper($method);
}
public function getMethod(){
return $this->method;
}
// this was no longer working --- https://github.com/guzzle/psr7/blob/master/src/functions.php
public static function parseQuery($query){
$result = array();
parse_str($query, $result);
return $result;
}
public function setUrl($url){
// remove hashtag - preg_replace so we don't have to check for its existence first - is it possible preserving hashtag?
$url = preg_replace('/#.*/', '', $url);
// check if url has any query parameters
$query = parse_url($url, PHP_URL_QUERY);
// remove it and add the query params to get collection
if($query){
//$url = str_replace('?'.$query, '', $url);
$url = preg_replace('/\?.*/', '', $url);
$result = self::parseQuery($query);
$this->get->replace($result);
}
// url without query params - those will be appended later
$this->url = $url;
$this->headers->set('host', parse_url($url, PHP_URL_HOST));
}
public function getRawHeaders(){
$result = array();
$headers = $this->headers->all();
// Sort headers by name
//ksort($headers);
// Turn this into name=value pairs
foreach($headers as $name => $values){
// could be an array if multiple headers are sent with the same name?
foreach( (array)$values as $value){
$name = implode('-', array_map('ucfirst', explode('-', $name)));
$result[] = sprintf("%s: %s", $name, $value);
}
}
return implode("\r\n", $result);
}
public function getUrl(){
// does this URL have any query parameters?
if($this->get->all()){
return $this->url.'?'.http_build_query($this->get->all());
}
return $this->url;
}
public function getUri(){
return call_user_func_array(array($this, "getUrl"), func_get_args());
}
public function setProtocolVersion($version){
$this->protocol_version = $version;
}
public function getProtocolVersion(){
return $this->protocol_version;
}
// Set raw contents of the body
// this will clear all the values currently stored in POST and FILES
// will be ignored during PREPARE if post or files contain any values
public function setBody($body, $content_type = false){
// clear old body data
$this->post->clear();
$this->files->clear();
// is this form data?
if(is_array($body)){
$body = http_build_query($body);
}
$this->body = (string)$body;
// plain text should be: text/plain; charset=UTF-8
if($content_type){
$this->headers->set('content-type', $content_type);
}
// do it!
$this->prepare();
}
private static function generateBoundary(){
return '-----'.md5(microtime().rand());
}
// can be $_POST and $_FILES
public static function buildPostBody($fields, $files, $boundary = null){
// the reason BODY part is not included in sprintf pattern is because of limits
$part_field = "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n";
$part_file = "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n";
// each part should be preceeded by this line
if(!$boundary){
$boundary = self::generateBoundary();
}
$body = '';
foreach($fields as $name => $value){
$body .= sprintf($part_field, $boundary, $name, $value);
$body .= "{$value}\r\n";
}
// data better have [name, tmp_name, and optional type]
foreach($files as $name => $values) {
// Multiple files can be uploaded using different name for input.
// See http://php.net/manual/en/features.file-upload.multiple.php
if (!is_array($values['tmp_name'])) {
$multiValues = array_map(function ($a) {
return (array)$a;
}, $values);
$fieldName = $name;
} else {
$multiValues = $values;
$fieldName = "{$name}[]";
}
foreach (array_keys($multiValues['tmp_name']) as $key) {
// There must be no error http://php.net/manual/en/features.file-upload.errors.php
if (!$multiValues['tmp_name'][$key] || $multiValues['error'][$key] !== 0 || !is_readable($multiValues['tmp_name'][$key])) {
continue;
}
$body .= sprintf($part_file, $boundary, $fieldName, $multiValues['name'][$key], $multiValues['type'][$key]);
$body .= file_get_contents($multiValues['tmp_name'][$key]);
$body .= "\r\n";
}
}
$body .= "--{$boundary}--\r\n\r\n";
return $body;
}
private function detectContentType($data){
// http://www.w3.org/Protocols/rfc1341/4_Content-Type.html
// If the media type remains unknown, the recipient SHOULD treat it as type "application/octet-stream".
$content_type = 'application/octet-stream';
if(preg_match('/^{\s*"[^"]+"\s*:/', $data)){
$content_type = 'application/json';
} else if(preg_match('/^(?:<\?xml[^?>]+\?>)\s*<[^>]+>/i', $data)){
$content_type = 'application/xml';
} else if(preg_match('/^[a-zA-Z0-9_.~-]+=[^&]*&/', $data)){
$content_type = 'application/x-www-form-urlencoded';
}
return $content_type;
}
// Returns a parsed version of the body
/*
public function getBody(){
// what is the content type?
$content_type = $this->headers->get('content-type', '');
switch($content_type){
case 'application/x-www-form-urlencoded':
$result = array();
mb_parse_str($this->body, $result);
return $result;
case 'application/json':
return json_decode($this->body);
case 'text/xml':
case 'application/xml':
case 'application/x-xml':
return simplexml_load_string($this->body);
}
return null;
}
*/
// Returns raw body string exactly as it appears in the HTTP request
public function getRawBody(){
return $this->prepared_body;
}
public static function createFromGlobals(){
$method = $_SERVER['REQUEST_METHOD'];
$scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']) ? 'https' : 'http';
$url = $scheme.'://'. $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$request = new Request($method, $url);
// fill in headers
foreach($_SERVER as $name => $value){
if(strpos($name, 'HTTP_') === 0){
$name = substr($name, 5);
$name = str_replace('_', ' ', $name);
$name = ucwords(strtolower($name));
$name = str_replace(' ', '-', $name);
$request->headers->set($name, $value);
}
}
// for extra convenience
//$request->params->set('user-ip', $_SERVER['REMOTE_ADDR']);
// will be empty if content-type is multipart
$input = file_get_contents("php://input");
if(count($_FILES) > 0){
$request->post->replace($_POST);
$request->files->replace($_FILES);
} else if(count($_POST) > 0){
$request->post->replace($_POST);
} else {
$request->setBody($input);
}
// for extra convenience
$request->prepare();
return $request;
}
}
?>

View File

@ -0,0 +1,120 @@
<?php
namespace Proxy\Http;
use Proxy\Http\ParamStore;
class Response {
protected $statusCodes = array(
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Time-out',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Large',
415 => 'Unsupported Media Type',
416 => 'Requested range not satisfiable',
417 => 'Expectation Failed',
429 => 'Too Many Requests',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Time-out',
505 => 'Unsupported Version'
);
public $status;
public $headers;
private $content;
// getHeaderLines
public function __construct($content = '', $status = 200, $headers = array()){
$this->headers = new ParamStore($headers);
$this->setContent($content);
$this->setStatusCode($status);
}
public function setStatusCode($code){
$this->status = $code;
}
public function getStatusCode(){
return $this->status;
}
public function getStatusText(){
return $this->statusCodes[$this->getStatusCode()];
}
public function setContent($content){
$this->content = (string)$content;
}
public function getContent(){
return $this->content;
}
public function sendHeaders(){
if(headers_sent()){
return;
}
header(sprintf('HTTP/1.1 %s %s', $this->status, $this->getStatusText()), true, $this->status);
foreach($this->headers->all() as $name => $value){
/*
Multiple message-header fields with the same field-name MAY be present in a message
if and only if the entire field-value for that header field is defined as a comma-separated list
http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
*/
$values = is_array($value) ? $value : array($value);
// false = do not replace previous identical header
foreach($values as $value){
header("{$name}: {$value}", false);
}
}
}
public function send(){
$this->sendHeaders();
echo $this->content;
}
}
?>

View File

@ -0,0 +1,80 @@
<?php
namespace Proxy\Plugin;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Proxy\Event\ProxyEvent;
abstract class AbstractPlugin implements EventSubscriberInterface {
// apply these methods only to those events whose request URL passes this filter
protected $url_pattern;
public function onBeforeRequest(ProxyEvent $event){
// fired right before a request is being sent to a proxy
}
public function onHeadersReceived(ProxyEvent $event){
// fired right after response headers have been fully received - last chance to modify before sending it back to the user
}
public function onCurlWrite(ProxyEvent $event){
// fired as the data is being written piece by piece
}
public function onCompleted(ProxyEvent $event){
// fired after the full response=headers+body has been read - will only be called on "non-streaming" responses
}
// dispatch based on filter
final public function route(ProxyEvent $event, $event_name, EventDispatcherInterface $dispatcher){
$url = $event['request']->getUri();
// url filter provided and current request url does not match it
if($this->url_pattern){
if(strpos($this->url_pattern, '/') === 0){
if(!preg_match($this->url_pattern, $url))
return;
}
else
{
if(stripos($url, $this->url_pattern) === false)
return;
}
}
switch($event_name){
case 'request.before_send':
$this->onBeforeRequest($event);
break;
case 'request.sent':
$this->onHeadersReceived($event);
break;
case 'curl.callback.write':
$this->onCurlWrite($event);
break;
case 'request.complete':
$this->onCompleted($event);
break;
}
}
// This method returns an array indexed by event names and whose values are either the method name to call
// or an array composed of the method name to call and a priority.
final public static function getSubscribedEvents(){
return array(
'request.before_send' => 'route',
'request.sent' => 'route',
'curl.callback.write' => 'route',
'request.complete' => 'route'
);
}
}
?>

View File

@ -0,0 +1,57 @@
<?php
use Proxy\Plugin\AbstractPlugin;
use Proxy\Event\ProxyEvent;
use Proxy\Config;
// https://proxylist.hidemyass.com/upload/
// TODO: this file is not found to be existant in ./plugins/ when namespace is specified
class BlockListPlugin extends AbstractPlugin {
function onBeforeRequest(ProxyEvent $event){
$user_ip = $_SERVER['REMOTE_ADDR'];
$user_ip_long = sprintf('%u', ip2long($user_ip));
$url = $event['request']->getUrl();
$url_host = parse_url($url, PHP_URL_HOST);
$fnc_custom = Config::get('blocklist.custom');
if(is_callable($fnc_custom)){
$ret = call_user_func($fnc_custom, compact('user_ip', 'user_ip_long', 'url', 'url_host') );
if(!$ret){
throw new \Exception("Error: Access Denied!");
}
return;
}
/*
1. Wildcard format: 1.2.3.*
2. CIDR format: 1.2.3/24 OR 1.2.3.4/255.255.255.0
3. Start-End IP format: 1.2.3.0-1.2.3.255
*/
$ip_match = false;
$action_block = true;
if(Config::has('blocklist.ip_allow')){
$ip_match = Config::get('blocklist.ip_allow');
$action_block = false;
} else if(Config::has('blocklist.ip_block')){
$ip_match = Config::get('blocklist.ip_block');
}
if($ip_match){
$m = re_match($ip_match, $user_ip);
// ip matched and we are in block_mode
// ip NOT matched and we are in allow mode
if( ($m && $action_block) || (!$m && !$action_block)){
throw new \Exception("Error: Access denied!");
}
}
}
}
?>

View File

@ -0,0 +1,132 @@
<?php
namespace Proxy\Plugin;
use Proxy\Plugin\AbstractPlugin;
use Proxy\Event\ProxyEvent;
class CookiePlugin extends AbstractPlugin {
const COOKIE_PREFIX = 'pc';
public function onBeforeRequest(ProxyEvent $event){
$request = $event['request'];
// cookie sent by the browser to the server
$http_cookie = $request->headers->get("cookie");
// remove old cookie header and rewrite it
$request->headers->remove("cookie");
/*
When the user agent generates an HTTP request, the user agent MUST NOT attach more than one Cookie header field.
http://tools.ietf.org/html/rfc6265#section-5.4
*/
$send_cookies = array();
// extract "proxy cookies" only
// A Proxy Cookie would have the following name: COOKIE_PREFIX_domain-it-belongs-to__cookie-name
if(preg_match_all('@pc_(.+?)__(.+?)=([^;]+)@', $http_cookie, $matches, PREG_SET_ORDER)){
foreach($matches as $match){
$cookie_name = $match[2];
$cookie_value = $match[3];
$cookie_domain = str_replace("_", ".", $match[1]);
// what is the domain or our current URL?
$host = parse_url($request->getUri(), PHP_URL_HOST);
// does this cookie belong to this domain?
// sometimes domain begins with a DOT indicating all subdomains - deprecated but still in use on some servers...
if(strpos($host, $cookie_domain) !== false){
$send_cookies[] = $cookie_name.'='.$cookie_value;
}
}
}
// do we have any cookies to send?
if($send_cookies){
$request->headers->set('cookie', implode("; ", $send_cookies));
}
}
// cookies received from a target server via set-cookie should be rewritten
public function onHeadersReceived(ProxyEvent $event){
$request = $event['request'];
$response = $event['response'];
// does the response send any cookies?
$set_cookie = $response->headers->get('set-cookie');
if($set_cookie){
// remove set-cookie header and reconstruct it differently
$response->headers->remove('set-cookie');
// loop through each set-cookie line
foreach( (array)$set_cookie as $line){
// parse cookie data as array from header line
$cookie = $this->parse_cookie($line, $request->getUri());
// construct a "proxy cookie" whose name includes the domain to which this cookie belongs to
// replace dots with underscores as cookie name can only contain alphanumeric and underscore
$cookie_name = sprintf("%s_%s__%s", self::COOKIE_PREFIX, str_replace('.', '_', $cookie['domain']), $cookie['name']);
// append a simple name=value cookie to the header - no expiration date means that the cookie will be a session cookie
$event['response']->headers->set('set-cookie', $cookie_name.'='.$cookie['value'], false);
}
}
}
// adapted from browserkit
private function parse_cookie($line, $url){
$host = parse_url($url, PHP_URL_HOST);
$data = array(
'name' => '',
'value' => '',
'domain' => $host,
'path' => '/',
'expires' => 0,
'secure' => false,
'httpOnly' => true
);
$line = preg_replace('/^Set-Cookie2?: /i', '', trim($line));
// there should be at least one name=value pair
$pairs = array_filter(array_map('trim', explode(';', $line)));
foreach($pairs as $index => $comp){
$parts = explode('=', $comp, 2);
$key = trim($parts[0]);
if(count($parts) == 1){
// secure; HttpOnly; == 1
$data[$key] = true;
} else {
$value = trim($parts[1]);
if($index == 0){
$data['name'] = $key;
$data['value'] = $value;
} else {
$data[$key] = $value;
}
}
}
return $data;
}
}
?>

View File

@ -0,0 +1,69 @@
<?php
namespace Proxy\Plugin;
use Proxy\Plugin\AbstractPlugin;
use Proxy\Event\ProxyEvent;
class HeaderRewritePlugin extends AbstractPlugin {
function onBeforeRequest(ProxyEvent $event){
// tell target website that we only accept plain text without any transformations
$event['request']->headers->set('accept-encoding', 'identity');
// mask proxy referer
$event['request']->headers->remove('referer');
}
function onHeadersReceived(ProxyEvent $event){
// so stupid... onCompleted won't be called on "streaming" responses
$response = $event['response'];
$request_url = $event['request']->getUri();
// proxify header location value
if($response->headers->has('location')){
$location = $response->headers->get('location');
// just in case this is a relative url like: /en
$response->headers->set('location', proxify_url($location, $request_url));
}
$code = $response->getStatusCode();
$text = $response->getStatusText();
if($code >= 400 && $code <= 600){
throw new \Exception("Error accessing resource: {$code} - {$text}");
}
// we need content-encoding (in case server refuses to serve it in plain text)
// content-length: final size of content sent to user may change via plugins, so it makes no sense to send old content-length
$forward_headers = array('content-type', 'zzzcontent-length', 'accept-ranges', 'content-range', 'content-disposition', 'location', 'set-cookie');
foreach($response->headers->all() as $name => $value){
// is this one of the headers we wish to forward back to the client?
if(!in_array($name, $forward_headers)){
$response->headers->remove($name);
}
}
if(!$response->headers->has('content-disposition')){
$url_path = parse_url($request_url, PHP_URL_PATH);
$filename = basename($url_path);
$response->headers->set('Content-Disposition', 'filename="'.$filename.'"');
}
// do not ever cache our proxy pages!
$response->headers->set("cache-control", "no-cache, no-store, must-revalidate");
$response->headers->set("pragma", "no-cache");
$response->headers->set("expires", 0);
}
}
?>

View File

@ -0,0 +1,185 @@
<?php
namespace Proxy\Plugin;
use Proxy\Plugin\AbstractPlugin;
use Proxy\Event\ProxyEvent;
use Proxy\Config;
use Proxy\Html;
class ProxifyPlugin extends AbstractPlugin {
private $base_url = '';
private function css_url($matches){
$url = trim($matches[1]);
if(stripos($url, 'data:') === 0){
return $matches[0];
}
return str_replace($matches[1], proxify_url($matches[1], $this->base_url), $matches[0]);
}
// this.params.logoImg&&(e="background-image: url("+this.params.logoImg+")")
private function css_import($matches){
return str_replace($matches[2], proxify_url($matches[2], $this->base_url), $matches[0]);
}
// replace src= and href=
private function html_attr($matches){
// could be empty?
$url = trim($matches[2]);
if(stripos($url, 'data:') === 0 || stripos($url, 'magnet:') === 0 ){
return $matches[0];
}
return str_replace($url, proxify_url($url, $this->base_url), $matches[0]);
}
private function form_action($matches){
// sometimes form action is empty - which means a postback to the current page
// $matches[1] holds single or double quote - whichever was used by webmaster
// $matches[2] holds form submit URL - can be empty which in that case should be replaced with current URL
if(!$matches[2]){
$matches[2] = $this->base_url;
}
$new_action = proxify_url($matches[2], $this->base_url);
// what is form method?
$form_post = preg_match('@method=(["\'])post\1@i', $matches[0]) == 1;
// take entire form string - find real url and replace it with proxified url
$result = str_replace($matches[2], $new_action, $matches[0]);
// must be converted to POST otherwise GET form would just start appending name=value pairs to your proxy url
if(!$form_post){
// may throw Duplicate Attribute warning but only first method matters
$result = str_replace("<form", '<form method="POST"', $result);
// got the idea from Glype - insert this input field to notify proxy later that this form must be converted to GET during http
$result .= '<input type="hidden" name="convertGET" value="1">';
}
return $result;
}
public function onBeforeRequest(ProxyEvent $event){
$request = $event['request'];
// check if one of the POST pairs is convertGET - if so, convert this request to GET
if($request->post->has('convertGET')){
// we don't need this parameter anymore
$request->post->remove('convertGET');
// replace all GET parameters with POST data
$request->get->replace($request->post->all());
// remove POST data
$request->post->clear();
// This is now a GET request
$request->setMethod('GET');
$request->prepare();
}
}
private function meta_refresh($matches){
$url = $matches[2];
return str_replace($url, proxify_url($url, $this->base_url), $matches[0]);
}
// <title>, <base>, <link>, <style>, <meta>, <script>, <noscript>
private function proxify_head($str){
// let's replace page titles with something custom
if(Config::get('replace_title')){
$str = preg_replace('/<title[^>]*>(.*?)<\/title>/is', '<title>'.Config::get('replace_title').'</title>', $str);
}
// base - update base_url contained in href - remove <base> tag entirely
//$str = preg_replace_callback('/<base[^>]*href=
// link - replace href with proxified
// link rel="shortcut icon" - replace or remove
// meta - only interested in http-equiv - replace url refresh
// <meta http-equiv="refresh" content="5; url=http://example.com/">
$str = preg_replace_callback('/content=(["\'])\d+\s*;\s*url=(.*?)\1/is', array($this, 'meta_refresh'), $str);
return $str;
}
// The <body> background attribute is not supported in HTML5. Use CSS instead.
private function proxify_css($str){
// The HTML5 standard does not require quotes around attribute values.
// if {1} is not there then youtube breaks for some reason
$str = preg_replace_callback('@[^a-z]{1}url\s*\((?:\'|"|)(.*?)(?:\'|"|)\)@im', array($this, 'css_url'), $str);
// https://developer.mozilla.org/en-US/docs/Web/CSS/@import
// TODO: what about @import directives that are outside <style>?
$str = preg_replace_callback('/@import (\'|")(.*?)\1/i', array($this, 'css_import'), $str);
return $str;
}
public function onCompleted(ProxyEvent $event){
// to be used when proxifying all the relative links
$this->base_url = $event['request']->getUri();
$response = $event['response'];
$content_type = $response->headers->get('content-type');
$str = $response->getContent();
// DO NOT do any proxification on .js files and text/plain content type
if($content_type == 'text/javascript' || $content_type == 'application/javascript' || $content_type == 'application/x-javascript' || $content_type == 'text/plain'){
return;
}
// remove JS from urls
$js_remove = Config::get('js_remove');
if(is_array($js_remove)){
$domain = parse_url($this->base_url, PHP_URL_HOST);
foreach($js_remove as $pattern){
if(strpos($domain, $pattern) !== false){
$str = Html::remove_scripts($str);
}
}
}
// add html.no-js
// let's remove all frames?? does not protect against the frames created dynamically via javascript
$str = preg_replace('@<iframe[^>]*>[^<]*<\\/iframe>@is', '', $str);
$str = $this->proxify_head($str);
$str = $this->proxify_css($str);
// src= and href=
$str = preg_replace_callback('@(?:src|href)\s*=\s*(["|\'])(.*?)\1@is', array($this, 'html_attr'), $str);
// form
$str = preg_replace_callback('@<form[^>]*action=(["\'])(.*?)\1[^>]*>@i', array($this, 'form_action'), $str);
$response->setContent($str);
}
}
?>

View File

@ -0,0 +1,57 @@
<?php
namespace Proxy\Plugin;
use Proxy\Plugin\AbstractPlugin;
use Proxy\Event\ProxyEvent;
// do not buffer certain responses... echo contents immediately, and exit when done
class StreamPlugin extends AbstractPlugin {
// stream: Set to true to stream a response body rather than download it all up front
private $output_buffer_types = array('text/html', 'text/plain', 'text/css', 'text/javascript', 'application/x-javascript', 'application/javascript');
private $stream = false;
// we stream response as it is received if its content length exceeds 5 megabytes
private $max_content_length = 5000000;
public function onHeadersReceived(ProxyEvent $event){
// what content type are we dealing with here? can be empty
$content_type = $event['response']->headers->get('content-type');
$content_type = clean_content_type($content_type);
// how big of data can we expect?
$content_length = $event['response']->headers->get('content-length');
// we stream if content is not of "text" content-type or if its size exceeds 5 megabytes
if(!in_array($content_type, $this->output_buffer_types) || $content_length > $this->max_content_length){
$this->stream = true;
$event['response']->sendHeaders();
// it is of no use for us to buffer the data since we're sending it out immediately, but sometimes we must do both, hence the parameter
if(!$event['request']->params->has('force_buffering')){
$event['proxy']->setOutputBuffering(false);
}
}
}
public function onCurlWrite(ProxyEvent $event){
if($this->stream){
echo $event['data'];
flush();
}
}
public function onCompleted(ProxyEvent $event){
// if this was a streaming response then exit the script immediately since we do not wish to process it any futher
if($this->stream){
exit;
}
}
}
?>

View File

@ -0,0 +1,168 @@
<?php
namespace Proxy;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\GenericEvent;
use Proxy\Config;
use Proxy\Event\ProxyEvent;
use Proxy\Http\Request;
use Proxy\Http\Response;
class Proxy {
// proxy version!
const VERSION = '5.0.1';
private $dispatcher;
private $request;
private $response;
private $output_buffering = true;
private $output_buffer = '';
private $status_found = false;
public function __construct(){
$this->dispatcher = new EventDispatcher();
}
public function setOutputBuffering($output_buffering){
$this->output_buffering = $output_buffering;
}
private function header_callback($ch, $headers){
$parts = explode(":", $headers, 2);
// extract status code
// if using proxy - we ignore this header: HTTP/1.1 200 Connection established
if(preg_match('/HTTP\/1.\d+ (\d+)/', $headers, $matches) && stripos($headers, '200 Connection established') === false){
$this->response->setStatusCode($matches[1]);
$this->status_found = true;
} else if(count($parts) == 2){
$name = strtolower($parts[0]);
$value = trim($parts[1]);
// this must be a header: value line
$this->response->headers->set($name, $value, false);
} else if($this->status_found){
// this is hacky but until anyone comes up with a better way...
$event = new ProxyEvent(array('request' => $this->request, 'response' => $this->response, 'proxy' => &$this));
// this is the end of headers - last line is always empty - notify the dispatcher about this
$this->dispatcher->dispatch('request.sent', $event);
}
return strlen($headers);
}
private function write_callback($ch, $str){
$len = strlen($str);
$this->dispatcher->dispatch('curl.callback.write', new ProxyEvent(array(
'request' => $this->request,
'data' => $str
)));
// Do we buffer this piece of data for later output or not?
if($this->output_buffering){
$this->output_buffer .= $str;
}
return $len;
}
public function getEventDispatcher(){
return $this->dispatcher;
}
public function forward(Request $request, $url){
// change request URL
$request->setUrl($url);
// prepare request and response objects
$this->request = $request;
$this->response = new Response();
$options = array(
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 0,
// don't return anything - we have other functions for that
CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false,
// don't bother with ssl
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
// we will take care of redirects
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_AUTOREFERER => false
);
// this is probably a good place to add custom curl options that way other critical options below would overwrite that
$config_options = Config::get('curl', array());
$options = array_merge_custom($options, $config_options);
$options[CURLOPT_HEADERFUNCTION] = array($this, 'header_callback');
$options[CURLOPT_WRITEFUNCTION] = array($this, 'write_callback');
// Notify any listeners that the request is ready to be sent, and this is your last chance to make any modifications.
$this->dispatcher->dispatch('request.before_send', new ProxyEvent(array('request' => $this->request, 'response' => $this->response)));
// We may not even need to send this request if response is already available somewhere (CachePlugin)
if($this->request->params->has('request.complete')){
// do nothing?
} else {
// any plugin might have changed our URL by this point
$options[CURLOPT_URL] = $this->request->getUri();
// fill in the rest of cURL options
$options[CURLOPT_HTTPHEADER] = explode("\r\n", $this->request->getRawHeaders());
$options[CURLOPT_CUSTOMREQUEST] = $this->request->getMethod();
$options[CURLOPT_POSTFIELDS] = $this->request->getRawBody();
$ch = curl_init();
curl_setopt_array($ch, $options);
// fetch the status - if exception if throw any at callbacks, then the error will be supressed
$result = @curl_exec($ch);
// there must have been an error if at this point
if(!$result){
$error = sprintf('(%d) %s', curl_errno($ch), curl_error($ch));
throw new \Exception($error);
}
// we have output waiting in the buffer?
$this->response->setContent($this->output_buffer);
// saves memory I would assume?
$this->output_buffer = null;
}
$this->dispatcher->dispatch('request.complete', new ProxyEvent(array('request' => $this->request, 'response' => $this->response)));
return $this->response;
}
}
?>

View File

@ -0,0 +1,30 @@
<?php
namespace Proxy;
class Redis {
protected static $client;
public function __construct(){
// do nothing
}
public static function __callStatic($method_name, $arguments){
$params = array(
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
);
if(!static::$client){
static::$client = new \Predis\Client($params);
}
return call_user_func_array(array(static::$client, $method_name), $arguments);
}
}
?>

View File

@ -0,0 +1,199 @@
<?php
use Proxy\Config;
// strip away extra parameters text/html; charset=UTF-8
function clean_content_type($content_type){
return trim(preg_replace('@;.*@', '', $content_type));
}
function is_html($content_type){
return clean_content_type($content_type) == 'text/html';
}
function in_arrayi($needle, $haystack){
return in_array(strtolower($needle), array_map('strtolower', $haystack));
}
function re_match($pattern, $string){
$quoted = preg_quote($pattern, '#');
$translated = strtr($quoted, array(
'\*' => '.*',
'\?' => '.'
));
return preg_match("#^".$translated."$#i", $string) === 1;
}
// regular array_merge does not work if arrays have numeric keys...
function array_merge_custom(){
$arr = array();
$args = func_get_args();
foreach( (array)$args as $arg){
foreach( (array)$arg as $key => $value){
$arr[$key] = $value;
}
}
return $arr;
}
// rotate each string character based on corresponding ascii values from some key
function str_rot_pass($str, $key, $decrypt = false){
// if key happens to be shorter than the data
$key_len = strlen($key);
$result = str_repeat(' ', strlen($str));
for($i=0; $i<strlen($str); $i++){
if($decrypt){
$ascii = ord($str[$i]) - ord($key[$i % $key_len]);
} else {
$ascii = ord($str[$i]) + ord($key[$i % $key_len]);
}
$result[$i] = chr($ascii);
}
return $result;
}
function app_url(){
return (!empty($_SERVER['HTTPS']) ? 'https://' : 'http://').$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'];
}
function render_string($str, $vars = array()){
preg_match_all('@{([a-z0-9_]+)}@s', $str, $matches, PREG_SET_ORDER);
foreach($matches as $match){
extract($vars, EXTR_PREFIX_ALL, "_var");
$var_val = ${"_var_".$match[1]};
$str = str_replace($match[0], $var_val, $str);
}
return $str;
}
function render_template($file_path, $vars = array()){
// variables to be used within that template
extract($vars);
ob_start();
if(file_exists($file_path)){
include($file_path);
} else {
die("Failed to load template: {$file_path}");
}
$contents = ob_get_contents();
ob_end_clean();
return $contents;
}
function add_http($url){
if(!preg_match('#^https?://#i', $url)){
$url = 'http://' . $url;
}
return $url;
}
function time_ms(){
return round(microtime(true) * 1000);
}
function base64_url_encode($input){
// = at the end is just padding to make the length of the str divisible by 4
return rtrim(strtr(base64_encode($input), '+/', '-_'), '=');
}
function base64_url_decode($input){
return base64_decode(str_pad(strtr($input, '-_', '+/'), strlen($input) % 4, '=', STR_PAD_RIGHT));
}
function url_encrypt($url, $key = false){
if($key){
$url = str_rot_pass($url, $key);
} else if(Config::get('encryption_key')){
$url = str_rot_pass($url, Config::get('encryption_key'));
}
return Config::get('url_mode') ? base64_url_encode($url) : rawurlencode($url);
}
function url_decrypt($url, $key = false){
$url = Config::get('url_mode') ? base64_url_decode($url) : rawurldecode($url);
if($key){
$url = str_rot_pass($url, $key, true);
} else if(Config::get('encryption_key')){
$url = str_rot_pass($url, Config::get('encryption_key'), true);
}
return $url;
}
// www.youtube.com TO proxy-app.com/index.php?q=encrypt_url(www.youtube.com)
function proxify_url($url, $base_url = ''){
$url = htmlspecialchars_decode($url);
if($base_url){
$base_url = add_http($base_url);
$url = rel2abs($url, $base_url);
}
return app_url().'?q='.url_encrypt($url);
}
function rel2abs($rel, $base)
{
if (strpos($rel, "//") === 0) {
return "http:" . $rel;
}
if($rel == ""){
return "";
}
/* return if already absolute URL */
if (parse_url($rel, PHP_URL_SCHEME) != '') return $rel;
/* queries and anchors */
if ($rel[0] == '#' || $rel[0] == '?') return $base . $rel;
/* parse base URL and convert to local variables:
$scheme, $host, $path */
extract(parse_url($base));
/* remove non-directory element from path */
@$path = preg_replace('#/[^/]*$#', '', $path);
/* destroy path if relative url points to root */
if ($rel[0] == '/') $path = '';
/* dirty absolute URL */
$abs = "$host$path/$rel";
/* replace '//' or '/./' or '/foo/../' with '/' */
$re = array(
'#(/\.?/)#',
'#/(?!\.\.)[^/]+/\.\./#'
);
for ($n = 1; $n > 0; $abs = preg_replace($re, '/', $abs, -1, $n)) {
}
/* absolute URL is ready! */
return $scheme . '://' . $abs;
}
?>

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016
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

@ -0,0 +1,81 @@
# youtube-downloader
This project was inspired by a very popular youtube-dl python package:
https://github.com/rg3/youtube-dl
Yes, there are multiple PHP-based youtube downloaders on the Internet, but all of them haven't been updated in years or they depend on youtube-dl.
This script does not depend on anything other than cURL. No Javascript interpreters, no calls to shell... nothing but pure PHP.
Demo
------
Just to prove its reliability and the fact that it works even with YouTube videos that encrypt their signature, visit this URL:
https://api.unblockvideos.com/youtube_downloader?id=e-ORhEE9VVg&selector=mp4
Or stream it directly:
https://api.unblockvideos.com/youtube_downloader?id=e-ORhEE9VVg&selector=mp4&redirect=true
Installation
-------
Recommended way of installing this is via [Composer](http://getcomposer.org):
```bash
composer require athlon1600/youtube-downloader
```
# Usage
```php
$yt = new YouTubeDownloader();
$links = $yt->getDownloadLinks("https://www.youtube.com/watch?v=QxsmWxxouIM");
var_dump($links);
```
Result:
```php
array(5) {
[22]=>
array(2) {
["url"]=>
string(670) "https://r3---sn-vgqs7ney.googlevideo.com/videoplayback?ratebypass=yes&requiressl=yes&initcwndbps=1142500&nh=IgpwZjAxLm9yZDM1Kg42Ni4yMDguMjI4LjIwMQ&key=yt6&mime=video%2Fmp4&mn=sn-vgqs7ney&mm=31&id=o-APybfQxBq_Uf0UwtAWdBuT2hoXzus5lvuXnd9VSmh5Dl&ip=67.184.200.25&gcr=us&sparams=dur%2Cei%2Cgcr%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cnh%2Cpl%2Cratebypass%2Crequiressl%2Csource%2Cupn%2Cexpire&mt=1482861742&ms=au&pl=16&itag=22&ei=Fq1iWM-PIsLyugLMor-gBA&mv=m&source=youtube&upn=pDkyvSW9InM&dur=265.357&ipbits=0&expire=1482883446&lmt=1478829845344913&signature=A27686411B20AD4EB61A29BC695509DB4D003681.9AE606614F809319EEE0B230BFCEFD09F5C39E12"
["format"]=>
string(13) "MP4 720p (HD)"
}
[43]=>
array(2) {
["url"]=>
string(704) "https://r3---sn-vgqs7ney.googlevideo.com/videoplayback?ratebypass=yes&requiressl=yes&initcwndbps=1142500&nh=IgpwZjAxLm9yZDM1Kg42Ni4yMDguMjI4LjIwMQ&key=yt6&gir=yes&mime=video%2Fwebm&mn=sn-vgqs7ney&mm=31&id=o-APybfQxBq_Uf0UwtAWdBuT2hoXzus5lvuXnd9VSmh5Dl&clen=23934795&ip=67.184.200.25&gcr=us&sparams=clen%2Cdur%2Cei%2Cgcr%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cnh%2Cpl%2Cratebypass%2Crequiressl%2Csource%2Cupn%2Cexpire&mt=1482861742&ms=au&pl=16&itag=43&ei=Fq1iWM-PIsLyugLMor-gBA&mv=m&source=youtube&upn=pDkyvSW9InM&dur=0.000&ipbits=0&expire=1482883446&lmt=1466552369088504&signature=4A9B43F989EF4DB937C56AC889BF9AFAA1363439.87B358B93A19E3C292BE823A2E7FD505E527956C"
["format"]=>
string(9) "WebM 360p"
}
[18]=>
array(2) {
["url"]=>
string(705) "https://r3---sn-vgqs7ney.googlevideo.com/videoplayback?ratebypass=yes&requiressl=yes&initcwndbps=1142500&nh=IgpwZjAxLm9yZDM1Kg42Ni4yMDguMjI4LjIwMQ&key=yt6&gir=yes&mime=video%2Fmp4&mn=sn-vgqs7ney&mm=31&id=o-APybfQxBq_Uf0UwtAWdBuT2hoXzus5lvuXnd9VSmh5Dl&clen=18431345&ip=67.184.200.25&gcr=us&sparams=clen%2Cdur%2Cei%2Cgcr%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cnh%2Cpl%2Cratebypass%2Crequiressl%2Csource%2Cupn%2Cexpire&mt=1482861742&ms=au&pl=16&itag=18&ei=Fq1iWM-PIsLyugLMor-gBA&mv=m&source=youtube&upn=pDkyvSW9InM&dur=265.357&ipbits=0&expire=1482883446&lmt=1478827692757115&signature=0C2203FABCEBC01A0B154C109EA7A03EBB778A17.7FFE657A41B06ABB4729E03253A92E1AA5562D3E"
["format"]=>
string(8) "MP4 360p"
}
[36]=>
array(2) {
["url"]=>
string(677) "https://r3---sn-vgqs7ney.googlevideo.com/videoplayback?requiressl=yes&initcwndbps=1142500&nh=IgpwZjAxLm9yZDM1Kg42Ni4yMDguMjI4LjIwMQ&key=yt6&gir=yes&mime=video%2F3gpp&mn=sn-vgqs7ney&mm=31&id=o-APybfQxBq_Uf0UwtAWdBuT2hoXzus5lvuXnd9VSmh5Dl&clen=7400500&ip=67.184.200.25&gcr=us&sparams=clen%2Cdur%2Cei%2Cgcr%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cnh%2Cpl%2Crequiressl%2Csource%2Cupn%2Cexpire&mt=1482861742&ms=au&pl=16&itag=36&ei=Fq1iWM-PIsLyugLMor-gBA&mv=m&source=youtube&upn=pDkyvSW9InM&dur=265.404&ipbits=0&expire=1482883446&lmt=1466551971126275&signature=121F4D6F18C10D31951E2C2A857E52351BCC1A8C.B6EFCCB6734190053A0F3980FC67D3E508EA30FF"
["format"]=>
string(7) "Unknown"
}
[17]=>
array(2) {
["url"]=>
string(677) "https://r3---sn-vgqs7ney.googlevideo.com/videoplayback?requiressl=yes&initcwndbps=1142500&nh=IgpwZjAxLm9yZDM1Kg42Ni4yMDguMjI4LjIwMQ&key=yt6&gir=yes&mime=video%2F3gpp&mn=sn-vgqs7ney&mm=31&id=o-APybfQxBq_Uf0UwtAWdBuT2hoXzus5lvuXnd9VSmh5Dl&clen=2661359&ip=67.184.200.25&gcr=us&sparams=clen%2Cdur%2Cei%2Cgcr%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cnh%2Cpl%2Crequiressl%2Csource%2Cupn%2Cexpire&mt=1482861742&ms=au&pl=16&itag=17&ei=Fq1iWM-PIsLyugLMor-gBA&mv=m&source=youtube&upn=pDkyvSW9InM&dur=265.404&ipbits=0&expire=1482883446&lmt=1466551946325771&signature=9C0017C6EE3EC754BCB73E4546483807143CC495.A738E50D2B2C9073E9369A8828BA780B6BA453F7"
["format"]=>
string(8) "3GP 144p"
}
}
```

View File

@ -0,0 +1,14 @@
{
"name": "athlon1600/youtube-downloader",
"description": "PHP powered alternative for youtube-dl",
"keywords": ["youtube downloader", "download youtube", "download youtube videos"],
"license": "MIT",
"require": {
"ext-curl": "*"
},
"autoload": {
"files": [
"src/YouTubeDownloader.php"
]
}
}

View File

@ -0,0 +1,388 @@
<?php
// utils.php
function sig_js_decode($player_html){
// what javascript function is responsible for signature decryption?
// var l=f.sig||Xn(f.s)
// a.set("signature",Xn(c));return a
if(preg_match('/signature",([a-zA-Z0-9$]+)\(/', $player_html, $matches)){
$func_name = $matches[1];
$func_name = preg_quote($func_name);
// extract code block from that function
// single quote in case function name contains $dollar sign
// xm=function(a){a=a.split("");wm.zO(a,47);wm.vY(a,1);wm.z9(a,68);wm.zO(a,21);wm.z9(a,34);wm.zO(a,16);wm.z9(a,41);return a.join("")};
if(preg_match('/'.$func_name.'=function\([a-z]+\){(.*?)}/', $player_html, $matches)){
$js_code = $matches[1];
// extract all relevant statements within that block
// wm.vY(a,1);
if(preg_match_all('/([a-z0-9]{2})\.([a-z0-9]{2})\([^,]+,(\d+)\)/i', $js_code, $matches) != false){
// must be identical
$obj_list = $matches[1];
//
$func_list = $matches[2];
// extract javascript code for each one of those statement functions
preg_match_all('/('.implode('|', $func_list).'):function(.*?)\}/m', $player_html, $matches2, PREG_SET_ORDER);
$functions = array();
// translate each function according to its use
foreach($matches2 as $m){
if(strpos($m[2], 'splice') !== false){
$functions[$m[1]] = 'splice';
} else if(strpos($m[2], 'a.length') !== false){
$functions[$m[1]] = 'swap';
} else if(strpos($m[2], 'reverse') !== false){
$functions[$m[1]] = 'reverse';
}
}
// FINAL STEP! convert it all to instructions set
$instructions = array();
foreach($matches[2] as $index => $name){
$instructions[] = array($functions[$name], $matches[3][$index]);
}
return $instructions;
}
}
}
return false;
}
// YouTube is capitalized twice because that's how youtube itself does it:
// https://developers.google.com/youtube/v3/code_samples/php
class YouTubeDownloader {
private $storage_dir;
private $cookie_dir;
private $itag_info = array(
18 => "MP4 360p",
22 => "MP4 720p (HD)",
37 => "MP4 1080",
38 => "MP4 3072p",
// questionable MP4s
59 => "MP4 480p",
78 => "MP4 480p",
43 => "WebM 360p",
17 => "3GP 144p"
);
function __construct(){
$this->storage_dir = sys_get_temp_dir();
$this->cookie_dir = sys_get_temp_dir();
}
function setStorageDir($dir){
$this->storage_dir = $dir;
}
// what identifies each request? user agent, cookies...
public function curl($url){
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
//curl_setopt($ch, CURLOPT_COOKIEJAR, $tmpfname);
//curl_setopt($ch, CURLOPT_COOKIEFILE, $tmpfname);
//curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
public static function head($url){
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
curl_setopt($ch, CURLOPT_NOBODY, 1);
$result = curl_exec($ch);
curl_close($ch);
return http_parse_headers($result);
}
// html code of watch?v=aaa
private function getInstructions($html){
// <script src="//s.ytimg.com/yts/jsbin/player-fr_FR-vflHVjlC5/base.js" name="player/base"></script>
// check what player version that video is using
if(preg_match('@<script\s*src="([^"]+player[^"]+js)@', $html, $matches)){
$player_url = $matches[1];
// relative protocol?
if(strpos($player_url, '//') === 0){
$player_url = 'http://'.substr($player_url, 2);
} else if(strpos($player_url, '/') === 0){
// relative path?
$player_url = 'http://www.youtube.com'.$player_url;
}
// try to find instructions list already cached from previous requests...
$file_path = $this->storage_dir.'/'.md5($player_url);
if(file_exists($file_path)){
// unserialize could fail on empty file
$str = file_get_contents($file_path);
return unserialize($str);
} else {
$js_code = $this->curl($player_url);
$instructions = sig_js_decode($js_code);
if($instructions){
file_put_contents($file_path, serialize($instructions));
return $instructions;
}
}
}
return false;
}
// this is in beta mode!!
public function stream($id){
$links = $this->getDownloadLinks($id, "mp4");
if(count($links) == 0){
die("no url found!");
}
// grab first available MP4 link
$url = $links[0]['url'];
// request headers
$headers = array(
'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0'
);
if(isset($_SERVER['HTTP_RANGE'])){
$headers[] = 'Range: '.$_SERVER['HTTP_RANGE'];
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
// we deal with this ourselves
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
curl_setopt($ch, CURLOPT_HEADER, 0);
// whether request to video success
$headers = '';
$headers_sent = false;
$success = false;
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($ch, $data) use (&$headers, &$headers_sent){
$headers .= $data;
// this should be first line
if(preg_match('@HTTP\/\d\.\d\s(\d+)@', $data, $matches)){
$status_code = $matches[1];
// status=ok or partial content
if($status_code == 200 || $status_code == 206){
$headers_sent = true;
header(rtrim($data));
}
} else {
// only headers we wish to forward back to the client
$forward = array('content-type', 'content-length', 'accept-ranges', 'content-range');
$parts = explode(':', $data, 2);
if($headers_sent && count($parts) == 2 && in_array(trim(strtolower($parts[0])), $forward)){
header(rtrim($data));
}
}
return strlen($data);
});
// if response is empty - this never gets called
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($curl, $data) use (&$headers_sent){
if($headers_sent){
echo $data;
flush();
}
return strlen($data);
});
$ret = @curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
// if we are still here by now, return status_code
return true;
}
// extract youtube video_id from any piece of text
public function extractId($str){
if(preg_match('/[a-z0-9_-]{11}/i', $str, $matches)){
return $matches[0];
}
return false;
}
// selector by format: mp4 360,
private function selectFirst($links, $selector){
$result = array();
$formats = preg_split('/\s*,\s*/', $selector);
// has to be in this order
foreach($formats as $f){
foreach($links as $l){
if(stripos($l['format'], $f) !== false || $f == 'any'){
$result[] = $l;
}
}
}
return $result;
}
// options | deep_links | append_redirector
public function getDownloadLinks($id, $selector = false){
$result = array();
$instructions = array();
// you can input HTML of /watch? page directory instead of id
if(strpos($id, '<div id="player') !== false){
$html = $id;
} else {
$video_id = $this->extractId($id);
if(!$video_id){
return false;
}
$html = $this->curl("https://www.youtube.com/watch?v={$video_id}");
}
// age-gate
if(strpos($html, 'player-age-gate-content') !== false){
// nothing you can do folks...
return false;
}
// http://stackoverflow.com/questions/35608686/how-can-i-get-the-actual-video-url-of-a-youtube-live-stream
if(preg_match('@url_encoded_fmt_stream_map["\']:\s*["\']([^"\'\s]*)@', $html, $matches)){
$parts = explode(",", $matches[1]);
foreach($parts as $p){
$query = str_replace('\u0026', '&', $p);
parse_str($query, $arr);
$url = $arr['url'];
if(isset($arr['sig'])){
$url = $url.'&signature='.$arr['sig'];
} else if(isset($arr['signature'])){
$url = $url.'&signature='.$arr['signature'];
} else if(isset($arr['s'])){
// this is probably a VEVO/ads video... signature must be decrypted first! We need instructions for doing that
if(count($instructions) == 0){
$instructions = (array)$this->getInstructions($html);
}
$dec = $this->sig_decipher($arr['s'], $instructions);
$url = $url.'&signature='.$dec;
}
// redirector.googlevideo.com
//$url = preg_replace('@(\/\/)[^\.]+(\.googlevideo\.com)@', '$1redirector$2', $url);
$itag = $arr['itag'];
$format = isset($this->itag_info[$itag]) ? $this->itag_info[$itag] : 'Unknown';
$result[$itag] = array(
'url' => $url,
'format' => $format
);
}
}
// do we want all links or just select few?
if($selector){
return $this->selectFirst($result, $selector);
}
return $result;
}
private function sig_decipher($signature, $instructions){
foreach($instructions as $opt){
$command = $opt[0];
$value = $opt[1];
if($command == 'swap'){
$temp = $signature[0];
$signature[0] = $signature[$value % strlen($signature)];
$signature[$value] = $temp;
} else if($command == 'splice'){
$signature = substr($signature, $value);
} else if($command == 'reverse'){
$signature = strrev($signature);
}
}
return trim($signature);
}
}
?>

7
vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInit63ec68ee21e36d1027e8f0238820c50e::getLoader();

413
vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,413 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
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.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative) {
return false;
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if ($file === null && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if ($file === null) {
// Remember that this class does not exist.
return $this->classMap[$class] = false;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

21
vendor/composer/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
Copyright (c) 2016 Nils Adermann, Jordi Boggiano
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.

9
vendor/composer/autoload_classmap.php vendored Normal file
View File

@ -0,0 +1,9 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

12
vendor/composer/autoload_files.php vendored Normal file
View File

@ -0,0 +1,12 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'5e97df31a48c3d8473c36e3f1c45e0a4' => $vendorDir . '/athlon1600/youtube-downloader/src/YouTubeDownloader.php',
'311a7016008bd2d1bccdd3da08cf87ee' => $vendorDir . '/athlon1600/php-proxy-plugin-bundle/src/utils.php',
'fe17454461a24db888b8da8720edd309' => $vendorDir . '/athlon1600/php-proxy/src/helpers.php',
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

12
vendor/composer/autoload_psr4.php vendored Normal file
View File

@ -0,0 +1,12 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
'Proxy\\Plugin\\' => array($vendorDir . '/athlon1600/php-proxy-plugin-bundle/src'),
'Proxy\\' => array($vendorDir . '/athlon1600/php-proxy/src'),
);

59
vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,59 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit63ec68ee21e36d1027e8f0238820c50e
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit63ec68ee21e36d1027e8f0238820c50e', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit63ec68ee21e36d1027e8f0238820c50e', 'loadClassLoader'));
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
$loader->register(true);
$includeFiles = require __DIR__ . '/autoload_files.php';
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire63ec68ee21e36d1027e8f0238820c50e($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequire63ec68ee21e36d1027e8f0238820c50e($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}

182
vendor/composer/installed.json vendored Normal file
View File

@ -0,0 +1,182 @@
[
{
"name": "athlon1600/youtube-downloader",
"version": "v1.0.1",
"version_normalized": "1.0.1.0",
"source": {
"type": "git",
"url": "https://github.com/Athlon1600/youtube-downloader.git",
"reference": "50fc4f3ac7e2daf002b9ce21aee95252f87b3ae9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Athlon1600/youtube-downloader/zipball/50fc4f3ac7e2daf002b9ce21aee95252f87b3ae9",
"reference": "50fc4f3ac7e2daf002b9ce21aee95252f87b3ae9",
"shasum": ""
},
"require": {
"ext-curl": "*"
},
"time": "2017-02-26 19:17:47",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"src/YouTubeDownloader.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHP powered alternative for youtube-dl",
"keywords": [
"download youtube",
"download youtube videos",
"youtube downloader"
]
},
{
"name": "athlon1600/php-proxy-plugin-bundle",
"version": "dev-master",
"version_normalized": "9999999-dev",
"source": {
"type": "git",
"url": "https://github.com/Athlon1600/php-proxy-plugin-bundle.git",
"reference": "172202c9b7913dc397b0a2eeacb68984a73f5c6e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Athlon1600/php-proxy-plugin-bundle/zipball/172202c9b7913dc397b0a2eeacb68984a73f5c6e",
"reference": "172202c9b7913dc397b0a2eeacb68984a73f5c6e",
"shasum": ""
},
"require": {
"athlon1600/youtube-downloader": "^1.0"
},
"time": "2017-03-17 19:59:40",
"type": "library",
"installation-source": "source",
"autoload": {
"psr-4": {
"Proxy\\Plugin\\": "src/"
},
"files": [
"src/utils.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A collection of useful plugins to be used with php-proxy"
},
{
"name": "symfony/event-dispatcher",
"version": "v3.3.2",
"version_normalized": "3.3.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "4054a102470665451108f9b59305c79176ef98f0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4054a102470665451108f9b59305c79176ef98f0",
"reference": "4054a102470665451108f9b59305c79176ef98f0",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"conflict": {
"symfony/dependency-injection": "<3.3"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.8|~3.0",
"symfony/dependency-injection": "~3.3",
"symfony/expression-language": "~2.8|~3.0",
"symfony/stopwatch": "~2.8|~3.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"time": "2017-06-04 18:15:29",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com"
},
{
"name": "athlon1600/php-proxy",
"version": "v5.0.3",
"version_normalized": "5.0.3.0",
"source": {
"type": "git",
"url": "https://github.com/Athlon1600/php-proxy.git",
"reference": "12584d6892779c719d26f79bf464e7d9a2b48f88"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Athlon1600/php-proxy/zipball/12584d6892779c719d26f79bf464e7d9a2b48f88",
"reference": "12584d6892779c719d26f79bf464e7d9a2b48f88",
"shasum": ""
},
"require": {
"ext-curl": "*",
"symfony/event-dispatcher": "~3.2"
},
"suggest": {
"predis/predis": "For caching purposes"
},
"time": "2017-05-13 18:26:27",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Proxy\\": "src/"
},
"files": [
"src/helpers.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"homepage": "https://www.php-proxy.com/",
"keywords": [
"php proxy",
"php proxy script",
"php web proxy",
"proxy script",
"web proxy"
]
}
]

View File

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View File

@ -0,0 +1,37 @@
CHANGELOG
=========
3.3.0
-----
* The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead.
3.0.0
-----
* The method `getListenerPriority($eventName, $listener)` has been added to the
`EventDispatcherInterface`.
* The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()`
and `Event::getName()` have been removed.
The event dispatcher and the event name are passed to the listener call.
2.5.0
-----
* added Debug\TraceableEventDispatcher (originally in HttpKernel)
* changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface
* added RegisterListenersPass (originally in HttpKernel)
2.1.0
-----
* added TraceableEventDispatcherInterface
* added ContainerAwareEventDispatcher
* added a reference to the EventDispatcher on the Event
* added a reference to the Event name on the event
* added fluid interface to the dispatch() method which now returns the Event
object
* added GenericEvent event class
* added the possibility for subscribers to subscribe several times for the
same event
* added ImmutableEventDispatcher

View File

@ -0,0 +1,211 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Lazily loads listeners and subscribers from the dependency injection
* container.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Jordan Alliot <jordan.alliot@gmail.com>
*
* @deprecated since 3.3, to be removed in 4.0. Use EventDispatcher with closure factories instead.
*/
class ContainerAwareEventDispatcher extends EventDispatcher
{
/**
* The container from where services are loaded.
*
* @var ContainerInterface
*/
private $container;
/**
* The service IDs of the event listeners and subscribers.
*
* @var array
*/
private $listenerIds = array();
/**
* The services registered as listeners.
*
* @var array
*/
private $listeners = array();
/**
* Constructor.
*
* @param ContainerInterface $container A ContainerInterface instance
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$class = get_class($this);
if ($this instanceof \PHPUnit_Framework_MockObject_MockObject || $this instanceof \Prophecy\Doubler\DoubleInterface) {
$class = get_parent_class($class);
}
if (__CLASS__ !== $class) {
@trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED);
}
}
/**
* Adds a service as event listener.
*
* @param string $eventName Event for which the listener is added
* @param array $callback The service ID of the listener service & the method
* name that has to be called
* @param int $priority The higher this value, the earlier an event listener
* will be triggered in the chain.
* Defaults to 0.
*
* @throws \InvalidArgumentException
*/
public function addListenerService($eventName, $callback, $priority = 0)
{
@trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED);
if (!is_array($callback) || 2 !== count($callback)) {
throw new \InvalidArgumentException('Expected an array("service", "method") argument');
}
$this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority);
}
public function removeListener($eventName, $listener)
{
$this->lazyLoad($eventName);
if (isset($this->listenerIds[$eventName])) {
foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method, $priority)) {
$key = $serviceId.'.'.$method;
if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) {
unset($this->listeners[$eventName][$key]);
if (empty($this->listeners[$eventName])) {
unset($this->listeners[$eventName]);
}
unset($this->listenerIds[$eventName][$i]);
if (empty($this->listenerIds[$eventName])) {
unset($this->listenerIds[$eventName]);
}
}
}
}
parent::removeListener($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
if (null === $eventName) {
return $this->listenerIds || $this->listeners || parent::hasListeners();
}
if (isset($this->listenerIds[$eventName])) {
return true;
}
return parent::hasListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
if (null === $eventName) {
foreach ($this->listenerIds as $serviceEventName => $args) {
$this->lazyLoad($serviceEventName);
}
} else {
$this->lazyLoad($eventName);
}
return parent::getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
$this->lazyLoad($eventName);
return parent::getListenerPriority($eventName, $listener);
}
/**
* Adds a service as event subscriber.
*
* @param string $serviceId The service ID of the subscriber service
* @param string $class The service's class name (which must implement EventSubscriberInterface)
*/
public function addSubscriberService($serviceId, $class)
{
@trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED);
foreach ($class::getSubscribedEvents() as $eventName => $params) {
if (is_string($params)) {
$this->listenerIds[$eventName][] = array($serviceId, $params, 0);
} elseif (is_string($params[0])) {
$this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0);
} else {
foreach ($params as $listener) {
$this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0);
}
}
}
}
public function getContainer()
{
@trigger_error('The '.__METHOD__.'() method is deprecated since version 3.3 as its class will be removed in 4.0. Inject the container or the services you need in your listeners/subscribers instead.', E_USER_DEPRECATED);
return $this->container;
}
/**
* Lazily loads listeners for this event from the dependency injection
* container.
*
* @param string $eventName The name of the event to dispatch. The name of
* the event is the name of the method that is
* invoked on listeners.
*/
protected function lazyLoad($eventName)
{
if (isset($this->listenerIds[$eventName])) {
foreach ($this->listenerIds[$eventName] as list($serviceId, $method, $priority)) {
$listener = $this->container->get($serviceId);
$key = $serviceId.'.'.$method;
if (!isset($this->listeners[$eventName][$key])) {
$this->addListener($eventName, array($listener, $method), $priority);
} elseif ($listener !== $this->listeners[$eventName][$key]) {
parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method));
$this->addListener($eventName, array($listener, $method), $priority);
}
$this->listeners[$eventName][$key] = $listener;
}
}
}
}

View File

@ -0,0 +1,324 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Stopwatch\Stopwatch;
use Psr\Log\LoggerInterface;
/**
* Collects some data about event listeners.
*
* This event dispatcher delegates the dispatching to another one.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableEventDispatcher implements TraceableEventDispatcherInterface
{
protected $logger;
protected $stopwatch;
private $called;
private $dispatcher;
private $wrappedListeners;
/**
* Constructor.
*
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
* @param Stopwatch $stopwatch A Stopwatch instance
* @param LoggerInterface $logger A LoggerInterface instance
*/
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
{
$this->dispatcher = $dispatcher;
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->called = array();
$this->wrappedListeners = array();
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
$this->dispatcher->addListener($eventName, $listener, $priority);
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
$this->dispatcher->addSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
$listener = $wrappedListener;
unset($this->wrappedListeners[$eventName][$index]);
break;
}
}
}
return $this->dispatcher->removeListener($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
return $this->dispatcher->removeSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
// we might have wrapped listeners for the event (if called while dispatching)
// in that case get the priority by wrapper
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
}
}
}
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
return $this->dispatcher->hasListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
{
if (null === $event) {
$event = new Event();
}
if (null !== $this->logger && $event->isPropagationStopped()) {
$this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
}
$this->preProcess($eventName);
$this->preDispatch($eventName, $event);
$e = $this->stopwatch->start($eventName, 'section');
$this->dispatcher->dispatch($eventName, $event);
if ($e->isStarted()) {
$e->stop();
}
$this->postDispatch($eventName, $event);
$this->postProcess($eventName);
return $event;
}
/**
* {@inheritdoc}
*/
public function getCalledListeners()
{
$called = array();
foreach ($this->called as $eventName => $listeners) {
foreach ($listeners as $listener) {
$called[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName);
}
}
return $called;
}
/**
* {@inheritdoc}
*/
public function getNotCalledListeners()
{
try {
$allListeners = $this->getListeners();
} catch (\Exception $e) {
if (null !== $this->logger) {
$this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e));
}
// unable to retrieve the uncalled listeners
return array();
}
$notCalled = array();
foreach ($allListeners as $eventName => $listeners) {
foreach ($listeners as $listener) {
$called = false;
if (isset($this->called[$eventName])) {
foreach ($this->called[$eventName] as $l) {
if ($l->getWrappedListener() === $listener) {
$called = true;
break;
}
}
}
if (!$called) {
if (!$listener instanceof WrappedListener) {
$listener = new WrappedListener($listener, null, $this->stopwatch, $this);
}
$notCalled[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName);
}
}
}
uasort($notCalled, array($this, 'sortListenersByPriority'));
return $notCalled;
}
/**
* Proxies all method calls to the original event dispatcher.
*
* @param string $method The method name
* @param array $arguments The method arguments
*
* @return mixed
*/
public function __call($method, $arguments)
{
return call_user_func_array(array($this->dispatcher, $method), $arguments);
}
/**
* Called before dispatching the event.
*
* @param string $eventName The event name
* @param Event $event The event
*/
protected function preDispatch($eventName, Event $event)
{
}
/**
* Called after dispatching the event.
*
* @param string $eventName The event name
* @param Event $event The event
*/
protected function postDispatch($eventName, Event $event)
{
}
private function preProcess($eventName)
{
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
$priority = $this->getListenerPriority($eventName, $listener);
$wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this);
$this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $wrappedListener, $priority);
}
}
private function postProcess($eventName)
{
unset($this->wrappedListeners[$eventName]);
$skipped = false;
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
continue;
}
// Unwrap listener
$priority = $this->getListenerPriority($eventName, $listener);
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
if (null !== $this->logger) {
$context = array('event' => $eventName, 'listener' => $listener->getPretty());
}
if ($listener->wasCalled()) {
if (null !== $this->logger) {
$this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
}
if (!isset($this->called[$eventName])) {
$this->called[$eventName] = new \SplObjectStorage();
}
$this->called[$eventName]->attach($listener);
}
if (null !== $this->logger && $skipped) {
$this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
}
if ($listener->stoppedPropagation()) {
if (null !== $this->logger) {
$this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
}
$skipped = true;
}
}
}
private function sortListenersByPriority($a, $b)
{
if (is_int($a['priority']) && !is_int($b['priority'])) {
return 1;
}
if (!is_int($a['priority']) && is_int($b['priority'])) {
return -1;
}
if ($a['priority'] === $b['priority']) {
return 0;
}
if ($a['priority'] > $b['priority']) {
return -1;
}
return 1;
}
}

View File

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
interface TraceableEventDispatcherInterface extends EventDispatcherInterface
{
/**
* Gets the called listeners.
*
* @return array An array of called listeners
*/
public function getCalledListeners();
/**
* Gets the not called listeners.
*
* @return array An array of not called listeners
*/
public function getNotCalledListeners();
}

View File

@ -0,0 +1,116 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\VarDumper\Caster\ClassStub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class WrappedListener
{
private $listener;
private $name;
private $called;
private $stoppedPropagation;
private $stopwatch;
private $dispatcher;
private $pretty;
private $stub;
private static $cloner;
public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
{
$this->listener = $listener;
$this->name = $name;
$this->stopwatch = $stopwatch;
$this->dispatcher = $dispatcher;
$this->called = false;
$this->stoppedPropagation = false;
if (is_array($listener)) {
$this->name = is_object($listener[0]) ? get_class($listener[0]) : $listener[0];
$this->pretty = $this->name.'::'.$listener[1];
} elseif ($listener instanceof \Closure) {
$this->pretty = $this->name = 'closure';
} elseif (is_string($listener)) {
$this->pretty = $this->name = $listener;
} else {
$this->name = get_class($listener);
$this->pretty = $this->name.'::__invoke';
}
if (null !== $name) {
$this->name = $name;
}
if (null === self::$cloner) {
self::$cloner = class_exists(ClassStub::class) ? new VarCloner() : false;
}
}
public function getWrappedListener()
{
return $this->listener;
}
public function wasCalled()
{
return $this->called;
}
public function stoppedPropagation()
{
return $this->stoppedPropagation;
}
public function getPretty()
{
return $this->pretty;
}
public function getInfo($eventName)
{
if (null === $this->stub) {
$this->stub = false === self::$cloner ? $this->pretty.'()' : new ClassStub($this->pretty.'()', $this->listener);
}
return array(
'event' => $eventName,
'priority' => null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null,
'pretty' => $this->pretty,
'stub' => $this->stub,
);
}
public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->called = true;
$e = $this->stopwatch->start($this->name, 'event_listener');
call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher);
if ($e->isStarted()) {
$e->stop();
}
if ($event->isPropagationStopped()) {
$this->stoppedPropagation = true;
}
}
}

View File

@ -0,0 +1,135 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Compiler pass to register tagged services for an event dispatcher.
*/
class RegisterListenersPass implements CompilerPassInterface
{
/**
* @var string
*/
protected $dispatcherService;
/**
* @var string
*/
protected $listenerTag;
/**
* @var string
*/
protected $subscriberTag;
/**
* Constructor.
*
* @param string $dispatcherService Service name of the event dispatcher in processed container
* @param string $listenerTag Tag name used for listener
* @param string $subscriberTag Tag name used for subscribers
*/
public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber')
{
$this->dispatcherService = $dispatcherService;
$this->listenerTag = $listenerTag;
$this->subscriberTag = $subscriberTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
return;
}
$definition = $container->findDefinition($this->dispatcherService);
foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) {
$def = $container->getDefinition($id);
foreach ($events as $event) {
$priority = isset($event['priority']) ? $event['priority'] : 0;
if (!isset($event['event'])) {
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
}
if (!isset($event['method'])) {
$event['method'] = 'on'.preg_replace_callback(array(
'/(?<=\b)[a-z]/i',
'/[^a-z0-9]/i',
), function ($matches) { return strtoupper($matches[0]); }, $event['event']);
$event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
}
$definition->addMethodCall('addListener', array($event['event'], array(new ServiceClosureArgument(new Reference($id)), $event['method']), $priority));
}
}
$extractingDispatcher = new ExtractingEventDispatcher();
foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $attributes) {
$def = $container->getDefinition($id);
// We must assume that the class value has been correctly filled, even if the service is created by a factory
$class = $container->getParameterBag()->resolveValue($def->getClass());
$interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
if (!is_subclass_of($class, $interface)) {
if (!class_exists($class, false)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
}
$container->addObjectResource($class);
ExtractingEventDispatcher::$subscriber = $class;
$extractingDispatcher->addSubscriber($extractingDispatcher);
foreach ($extractingDispatcher->listeners as $args) {
$args[1] = array(new ServiceClosureArgument(new Reference($id)), $args[1]);
$definition->addMethodCall('addListener', $args);
}
$extractingDispatcher->listeners = array();
}
}
}
/**
* @internal
*/
class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
{
public $listeners = array();
public static $subscriber;
public function addListener($eventName, $listener, $priority = 0)
{
$this->listeners[] = array($eventName, $listener[1], $priority);
}
public static function getSubscribedEvents()
{
$callback = array(self::$subscriber, 'getSubscribedEvents');
return $callback();
}
}

View File

@ -0,0 +1,58 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* Event is the base class for classes containing event data.
*
* This class contains no event data. It is used by events that do not pass
* state information to an event handler when an event is raised.
*
* You can call the method stopPropagation() to abort the execution of
* further listeners in your event listener.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class Event
{
/**
* @var bool Whether no further event listeners should be triggered
*/
private $propagationStopped = false;
/**
* Returns whether further event listeners should be triggered.
*
* @see Event::stopPropagation()
*
* @return bool Whether propagation was already stopped for this event
*/
public function isPropagationStopped()
{
return $this->propagationStopped;
}
/**
* Stops the propagation of the event to further event listeners.
*
* If multiple event listeners are connected to the same event, no
* further event listener will be triggered once any trigger calls
* stopPropagation().
*/
public function stopPropagation()
{
$this->propagationStopped = true;
}
}

View File

@ -0,0 +1,236 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
*
* Listeners are registered on the manager and events are dispatched through the
* manager.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Jordan Alliot <jordan.alliot@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class EventDispatcher implements EventDispatcherInterface
{
private $listeners = array();
private $sorted = array();
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
{
if (null === $event) {
$event = new Event();
}
if ($listeners = $this->getListeners($eventName)) {
$this->doDispatch($listeners, $eventName, $event);
}
return $event;
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
if (null !== $eventName) {
if (empty($this->listeners[$eventName])) {
return array();
}
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
return $this->sorted[$eventName];
}
foreach ($this->listeners as $eventName => $eventListeners) {
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
}
return array_filter($this->sorted);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
if (empty($this->listeners[$eventName])) {
return;
}
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
$listener[0] = $listener[0]();
}
foreach ($this->listeners[$eventName] as $priority => $listeners) {
foreach ($listeners as $k => $v) {
if ($v !== $listener && is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
$v[0] = $v[0]();
$this->listeners[$eventName][$priority][$k] = $v;
}
if ($v === $listener) {
return $priority;
}
}
}
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
if (null !== $eventName) {
return !empty($this->listeners[$eventName]);
}
foreach ($this->listeners as $eventListeners) {
if ($eventListeners) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
$this->listeners[$eventName][$priority][] = $listener;
unset($this->sorted[$eventName]);
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
if (empty($this->listeners[$eventName])) {
return;
}
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
$listener[0] = $listener[0]();
}
foreach ($this->listeners[$eventName] as $priority => $listeners) {
foreach ($listeners as $k => $v) {
if ($v !== $listener && is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
$v[0] = $v[0]();
}
if ($v === $listener) {
unset($listeners[$k], $this->sorted[$eventName]);
} else {
$listeners[$k] = $v;
}
}
if ($listeners) {
$this->listeners[$eventName][$priority] = $listeners;
} else {
unset($this->listeners[$eventName][$priority]);
}
}
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (is_string($params)) {
$this->addListener($eventName, array($subscriber, $params));
} elseif (is_string($params[0])) {
$this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
} else {
foreach ($params as $listener) {
$this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
}
}
}
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (is_array($params) && is_array($params[0])) {
foreach ($params as $listener) {
$this->removeListener($eventName, array($subscriber, $listener[0]));
}
} else {
$this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0]));
}
}
}
/**
* Triggers the listeners of an event.
*
* This method can be overridden to add functionality that is executed
* for each listener.
*
* @param callable[] $listeners The event listeners
* @param string $eventName The name of the event to dispatch
* @param Event $event The event object to pass to the event handlers/listeners
*/
protected function doDispatch($listeners, $eventName, Event $event)
{
foreach ($listeners as $listener) {
if ($event->isPropagationStopped()) {
break;
}
call_user_func($listener, $event, $eventName, $this);
}
}
/**
* Sorts the internal list of listeners for the given event by priority.
*
* @param string $eventName The name of the event
*/
private function sortListeners($eventName)
{
krsort($this->listeners[$eventName]);
$this->sorted[$eventName] = array();
foreach ($this->listeners[$eventName] as $priority => $listeners) {
foreach ($listeners as $k => $listener) {
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
$listener[0] = $listener[0]();
$this->listeners[$eventName][$priority][$k] = $listener;
}
$this->sorted[$eventName][] = $listener;
}
}
}
}

View File

@ -0,0 +1,100 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
* Listeners are registered on the manager and events are dispatched through the
* manager.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface EventDispatcherInterface
{
/**
* Dispatches an event to all registered listeners.
*
* @param string $eventName The name of the event to dispatch. The name of
* the event is the name of the method that is
* invoked on listeners.
* @param Event $event The event to pass to the event handlers/listeners
* If not supplied, an empty Event instance is created.
*
* @return Event
*/
public function dispatch($eventName, Event $event = null);
/**
* Adds an event listener that listens on the specified events.
*
* @param string $eventName The event to listen on
* @param callable $listener The listener
* @param int $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
*/
public function addListener($eventName, $listener, $priority = 0);
/**
* Adds an event subscriber.
*
* The subscriber is asked for all the events he is
* interested in and added as a listener for these events.
*
* @param EventSubscriberInterface $subscriber The subscriber
*/
public function addSubscriber(EventSubscriberInterface $subscriber);
/**
* Removes an event listener from the specified events.
*
* @param string $eventName The event to remove a listener from
* @param callable $listener The listener to remove
*/
public function removeListener($eventName, $listener);
/**
* Removes an event subscriber.
*
* @param EventSubscriberInterface $subscriber The subscriber
*/
public function removeSubscriber(EventSubscriberInterface $subscriber);
/**
* Gets the listeners of a specific event or all listeners sorted by descending priority.
*
* @param string $eventName The name of the event
*
* @return array The event listeners for the specified event, or all event listeners by event name
*/
public function getListeners($eventName = null);
/**
* Gets the listener priority for a specific event.
*
* Returns null if the event or the listener does not exist.
*
* @param string $eventName The name of the event
* @param callable $listener The listener
*
* @return int|null The event listener priority
*/
public function getListenerPriority($eventName, $listener);
/**
* Checks whether an event has any registered listeners.
*
* @param string $eventName The name of the event
*
* @return bool true if the specified event has any listeners, false otherwise
*/
public function hasListeners($eventName = null);
}

View File

@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* An EventSubscriber knows himself what events he is interested in.
* If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
* {@link getSubscribedEvents} and registers the subscriber as a listener for all
* returned events.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface EventSubscriberInterface
{
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2')))
*
* @return array The event names to listen to
*/
public static function getSubscribedEvents();
}

View File

@ -0,0 +1,186 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* Event encapsulation class.
*
* Encapsulates events thus decoupling the observer from the subject they encapsulate.
*
* @author Drak <drak@zikula.org>
*/
class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
{
/**
* Event subject.
*
* @var mixed usually object or callable
*/
protected $subject;
/**
* Array of arguments.
*
* @var array
*/
protected $arguments;
/**
* Encapsulate an event with $subject and $args.
*
* @param mixed $subject The subject of the event, usually an object
* @param array $arguments Arguments to store in the event
*/
public function __construct($subject = null, array $arguments = array())
{
$this->subject = $subject;
$this->arguments = $arguments;
}
/**
* Getter for subject property.
*
* @return mixed $subject The observer subject
*/
public function getSubject()
{
return $this->subject;
}
/**
* Get argument by key.
*
* @param string $key Key
*
* @return mixed Contents of array key
*
* @throws \InvalidArgumentException If key is not found.
*/
public function getArgument($key)
{
if ($this->hasArgument($key)) {
return $this->arguments[$key];
}
throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
}
/**
* Add argument to event.
*
* @param string $key Argument name
* @param mixed $value Value
*
* @return $this
*/
public function setArgument($key, $value)
{
$this->arguments[$key] = $value;
return $this;
}
/**
* Getter for all arguments.
*
* @return array
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Set args property.
*
* @param array $args Arguments
*
* @return $this
*/
public function setArguments(array $args = array())
{
$this->arguments = $args;
return $this;
}
/**
* Has argument.
*
* @param string $key Key of arguments array
*
* @return bool
*/
public function hasArgument($key)
{
return array_key_exists($key, $this->arguments);
}
/**
* ArrayAccess for argument getter.
*
* @param string $key Array key
*
* @return mixed
*
* @throws \InvalidArgumentException If key does not exist in $this->args.
*/
public function offsetGet($key)
{
return $this->getArgument($key);
}
/**
* ArrayAccess for argument setter.
*
* @param string $key Array key to set
* @param mixed $value Value
*/
public function offsetSet($key, $value)
{
$this->setArgument($key, $value);
}
/**
* ArrayAccess for unset argument.
*
* @param string $key Array key
*/
public function offsetUnset($key)
{
if ($this->hasArgument($key)) {
unset($this->arguments[$key]);
}
}
/**
* ArrayAccess has argument.
*
* @param string $key Array key
*
* @return bool
*/
public function offsetExists($key)
{
return $this->hasArgument($key);
}
/**
* IteratorAggregate for iterating over the object like an array.
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator($this->arguments);
}
}

View File

@ -0,0 +1,101 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* A read-only proxy for an event dispatcher.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ImmutableEventDispatcher implements EventDispatcherInterface
{
/**
* The proxied dispatcher.
*
* @var EventDispatcherInterface
*/
private $dispatcher;
/**
* Creates an unmodifiable proxy for an event dispatcher.
*
* @param EventDispatcherInterface $dispatcher The proxied event dispatcher
*/
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
{
return $this->dispatcher->dispatch($eventName, $event);
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
return $this->dispatcher->hasListeners($eventName);
}
}

19
vendor/symfony/event-dispatcher/LICENSE vendored Normal file
View File

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

View File

@ -0,0 +1,15 @@
EventDispatcher Component
=========================
The EventDispatcher component provides tools that allow your application
components to communicate with each other by dispatching events and listening to
them.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/event_dispatcher/index.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@ -0,0 +1,442 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
abstract class AbstractEventDispatcherTest extends TestCase
{
/* Some pseudo events */
const preFoo = 'pre.foo';
const postFoo = 'post.foo';
const preBar = 'pre.bar';
const postBar = 'post.bar';
/**
* @var EventDispatcher
*/
private $dispatcher;
private $listener;
protected function setUp()
{
$this->dispatcher = $this->createEventDispatcher();
$this->listener = new TestEventListener();
}
protected function tearDown()
{
$this->dispatcher = null;
$this->listener = null;
}
abstract protected function createEventDispatcher();
public function testInitialState()
{
$this->assertEquals(array(), $this->dispatcher->getListeners());
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
$this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
}
public function testAddListener()
{
$this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
$this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
$this->assertTrue($this->dispatcher->hasListeners());
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
$this->assertCount(1, $this->dispatcher->getListeners(self::preFoo));
$this->assertCount(1, $this->dispatcher->getListeners(self::postFoo));
$this->assertCount(2, $this->dispatcher->getListeners());
}
public function testGetListenersSortsByPriority()
{
$listener1 = new TestEventListener();
$listener2 = new TestEventListener();
$listener3 = new TestEventListener();
$listener1->name = '1';
$listener2->name = '2';
$listener3->name = '3';
$this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10);
$this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10);
$this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo'));
$expected = array(
array($listener2, 'preFoo'),
array($listener3, 'preFoo'),
array($listener1, 'preFoo'),
);
$this->assertSame($expected, $this->dispatcher->getListeners('pre.foo'));
}
public function testGetAllListenersSortsByPriority()
{
$listener1 = new TestEventListener();
$listener2 = new TestEventListener();
$listener3 = new TestEventListener();
$listener4 = new TestEventListener();
$listener5 = new TestEventListener();
$listener6 = new TestEventListener();
$this->dispatcher->addListener('pre.foo', $listener1, -10);
$this->dispatcher->addListener('pre.foo', $listener2);
$this->dispatcher->addListener('pre.foo', $listener3, 10);
$this->dispatcher->addListener('post.foo', $listener4, -10);
$this->dispatcher->addListener('post.foo', $listener5);
$this->dispatcher->addListener('post.foo', $listener6, 10);
$expected = array(
'pre.foo' => array($listener3, $listener2, $listener1),
'post.foo' => array($listener6, $listener5, $listener4),
);
$this->assertSame($expected, $this->dispatcher->getListeners());
}
public function testGetListenerPriority()
{
$listener1 = new TestEventListener();
$listener2 = new TestEventListener();
$this->dispatcher->addListener('pre.foo', $listener1, -10);
$this->dispatcher->addListener('pre.foo', $listener2);
$this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1));
$this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2));
$this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2));
$this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {}));
}
public function testDispatch()
{
$this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
$this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
$this->dispatcher->dispatch(self::preFoo);
$this->assertTrue($this->listener->preFooInvoked);
$this->assertFalse($this->listener->postFooInvoked);
$this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent'));
$this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo));
$event = new Event();
$return = $this->dispatcher->dispatch(self::preFoo, $event);
$this->assertSame($event, $return);
}
public function testDispatchForClosure()
{
$invoked = 0;
$listener = function () use (&$invoked) {
++$invoked;
};
$this->dispatcher->addListener('pre.foo', $listener);
$this->dispatcher->addListener('post.foo', $listener);
$this->dispatcher->dispatch(self::preFoo);
$this->assertEquals(1, $invoked);
}
public function testStopEventPropagation()
{
$otherListener = new TestEventListener();
// postFoo() stops the propagation, so only one listener should
// be executed
// Manually set priority to enforce $this->listener to be called first
$this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10);
$this->dispatcher->addListener('post.foo', array($otherListener, 'preFoo'));
$this->dispatcher->dispatch(self::postFoo);
$this->assertTrue($this->listener->postFooInvoked);
$this->assertFalse($otherListener->postFooInvoked);
}
public function testDispatchByPriority()
{
$invoked = array();
$listener1 = function () use (&$invoked) {
$invoked[] = '1';
};
$listener2 = function () use (&$invoked) {
$invoked[] = '2';
};
$listener3 = function () use (&$invoked) {
$invoked[] = '3';
};
$this->dispatcher->addListener('pre.foo', $listener1, -10);
$this->dispatcher->addListener('pre.foo', $listener2);
$this->dispatcher->addListener('pre.foo', $listener3, 10);
$this->dispatcher->dispatch(self::preFoo);
$this->assertEquals(array('3', '2', '1'), $invoked);
}
public function testRemoveListener()
{
$this->dispatcher->addListener('pre.bar', $this->listener);
$this->assertTrue($this->dispatcher->hasListeners(self::preBar));
$this->dispatcher->removeListener('pre.bar', $this->listener);
$this->assertFalse($this->dispatcher->hasListeners(self::preBar));
$this->dispatcher->removeListener('notExists', $this->listener);
}
public function testAddSubscriber()
{
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
}
public function testAddSubscriberWithPriorities()
{
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$eventSubscriber = new TestEventSubscriberWithPriorities();
$this->dispatcher->addSubscriber($eventSubscriber);
$listeners = $this->dispatcher->getListeners('pre.foo');
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertCount(2, $listeners);
$this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]);
}
public function testAddSubscriberWithMultipleListeners()
{
$eventSubscriber = new TestEventSubscriberWithMultipleListeners();
$this->dispatcher->addSubscriber($eventSubscriber);
$listeners = $this->dispatcher->getListeners('pre.foo');
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertCount(2, $listeners);
$this->assertEquals('preFoo2', $listeners[0][1]);
}
public function testRemoveSubscriber()
{
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
$this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
}
public function testRemoveSubscriberWithPriorities()
{
$eventSubscriber = new TestEventSubscriberWithPriorities();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
}
public function testRemoveSubscriberWithMultipleListeners()
{
$eventSubscriber = new TestEventSubscriberWithMultipleListeners();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertCount(2, $this->dispatcher->getListeners(self::preFoo));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
}
public function testEventReceivesTheDispatcherInstanceAsArgument()
{
$listener = new TestWithDispatcher();
$this->dispatcher->addListener('test', array($listener, 'foo'));
$this->assertNull($listener->name);
$this->assertNull($listener->dispatcher);
$this->dispatcher->dispatch('test');
$this->assertEquals('test', $listener->name);
$this->assertSame($this->dispatcher, $listener->dispatcher);
}
/**
* @see https://bugs.php.net/bug.php?id=62976
*
* This bug affects:
* - The PHP 5.3 branch for versions < 5.3.18
* - The PHP 5.4 branch for versions < 5.4.8
* - The PHP 5.5 branch is not affected
*/
public function testWorkaroundForPhpBug62976()
{
$dispatcher = $this->createEventDispatcher();
$dispatcher->addListener('bug.62976', new CallableClass());
$dispatcher->removeListener('bug.62976', function () {});
$this->assertTrue($dispatcher->hasListeners('bug.62976'));
}
public function testHasListenersWhenAddedCallbackListenerIsRemoved()
{
$listener = function () {};
$this->dispatcher->addListener('foo', $listener);
$this->dispatcher->removeListener('foo', $listener);
$this->assertFalse($this->dispatcher->hasListeners());
}
public function testGetListenersWhenAddedCallbackListenerIsRemoved()
{
$listener = function () {};
$this->dispatcher->addListener('foo', $listener);
$this->dispatcher->removeListener('foo', $listener);
$this->assertSame(array(), $this->dispatcher->getListeners());
}
public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled()
{
$this->assertFalse($this->dispatcher->hasListeners('foo'));
$this->assertFalse($this->dispatcher->hasListeners());
}
public function testHasListenersIsLazy()
{
$called = 0;
$listener = array(function () use (&$called) { ++$called; }, 'onFoo');
$this->dispatcher->addListener('foo', $listener);
$this->assertTrue($this->dispatcher->hasListeners());
$this->assertTrue($this->dispatcher->hasListeners('foo'));
$this->assertSame(0, $called);
}
public function testDispatchLazyListener()
{
$called = 0;
$factory = function () use (&$called) {
++$called;
return new TestWithDispatcher();
};
$this->dispatcher->addListener('foo', array($factory, 'foo'));
$this->assertSame(0, $called);
$this->dispatcher->dispatch('foo', new Event());
$this->dispatcher->dispatch('foo', new Event());
$this->assertSame(1, $called);
}
public function testRemoveFindsLazyListeners()
{
$test = new TestWithDispatcher();
$factory = function () use ($test) { return $test; };
$this->dispatcher->addListener('foo', array($factory, 'foo'));
$this->assertTrue($this->dispatcher->hasListeners('foo'));
$this->dispatcher->removeListener('foo', array($test, 'foo'));
$this->assertFalse($this->dispatcher->hasListeners('foo'));
$this->dispatcher->addListener('foo', array($test, 'foo'));
$this->assertTrue($this->dispatcher->hasListeners('foo'));
$this->dispatcher->removeListener('foo', array($factory, 'foo'));
$this->assertFalse($this->dispatcher->hasListeners('foo'));
}
public function testPriorityFindsLazyListeners()
{
$test = new TestWithDispatcher();
$factory = function () use ($test) { return $test; };
$this->dispatcher->addListener('foo', array($factory, 'foo'), 3);
$this->assertSame(3, $this->dispatcher->getListenerPriority('foo', array($test, 'foo')));
$this->dispatcher->removeListener('foo', array($factory, 'foo'));
$this->dispatcher->addListener('foo', array($test, 'foo'), 5);
$this->assertSame(5, $this->dispatcher->getListenerPriority('foo', array($factory, 'foo')));
}
public function testGetLazyListeners()
{
$test = new TestWithDispatcher();
$factory = function () use ($test) { return $test; };
$this->dispatcher->addListener('foo', array($factory, 'foo'), 3);
$this->assertSame(array(array($test, 'foo')), $this->dispatcher->getListeners('foo'));
$this->dispatcher->removeListener('foo', array($test, 'foo'));
$this->dispatcher->addListener('bar', array($factory, 'foo'), 3);
$this->assertSame(array('bar' => array(array($test, 'foo'))), $this->dispatcher->getListeners());
}
}
class CallableClass
{
public function __invoke()
{
}
}
class TestEventListener
{
public $preFooInvoked = false;
public $postFooInvoked = false;
/* Listener methods */
public function preFoo(Event $e)
{
$this->preFooInvoked = true;
}
public function postFoo(Event $e)
{
$this->postFooInvoked = true;
$e->stopPropagation();
}
}
class TestWithDispatcher
{
public $name;
public $dispatcher;
public function foo(Event $e, $name, $dispatcher)
{
$this->name = $name;
$this->dispatcher = $dispatcher;
}
}
class TestEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo');
}
}
class TestEventSubscriberWithPriorities implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
'pre.foo' => array('preFoo', 10),
'post.foo' => array('postFoo'),
);
}
}
class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array('pre.foo' => array(
array('preFoo1'),
array('preFoo2', 10),
));
}
}

View File

@ -0,0 +1,210 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* @group legacy
*/
class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest
{
protected function createEventDispatcher()
{
$container = new Container();
return new ContainerAwareEventDispatcher($container);
}
public function testAddAListenerService()
{
$event = new Event();
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$dispatcher->dispatch('onEvent', $event);
}
public function testAddASubscriberService()
{
$event = new Event();
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\SubscriberService')->getMock();
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$service
->expects($this->once())
->method('onEventWithPriority')
->with($event)
;
$service
->expects($this->once())
->method('onEventNested')
->with($event)
;
$container = new Container();
$container->set('service.subscriber', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService');
$dispatcher->dispatch('onEvent', $event);
$dispatcher->dispatch('onEventWithPriority', $event);
$dispatcher->dispatch('onEventNested', $event);
}
public function testPreventDuplicateListenerService()
{
$event = new Event();
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10);
$dispatcher->dispatch('onEvent', $event);
}
public function testHasListenersOnLazyLoad()
{
$event = new Event();
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$this->assertTrue($dispatcher->hasListeners());
if ($dispatcher->hasListeners('onEvent')) {
$dispatcher->dispatch('onEvent');
}
}
public function testGetListenersOnLazyLoad()
{
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$listeners = $dispatcher->getListeners();
$this->assertTrue(isset($listeners['onEvent']));
$this->assertCount(1, $dispatcher->getListeners('onEvent'));
}
public function testRemoveAfterDispatch()
{
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$dispatcher->dispatch('onEvent', new Event());
$dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
$this->assertFalse($dispatcher->hasListeners('onEvent'));
}
public function testRemoveBeforeDispatch()
{
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
$this->assertFalse($dispatcher->hasListeners('onEvent'));
}
}
class Service
{
public function onEvent(Event $e)
{
}
}
class SubscriberService implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
'onEvent' => 'onEvent',
'onEventWithPriority' => array('onEventWithPriority', 10),
'onEventNested' => array(array('onEventNested')),
);
}
public function onEvent(Event $e)
{
}
public function onEventWithPriority(Event $e)
{
}
public function onEventNested(Event $e)
{
}
}

View File

@ -0,0 +1,242 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests\Debug;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Stopwatch\Stopwatch;
class TraceableEventDispatcherTest extends TestCase
{
public function testAddRemoveListener()
{
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$tdispatcher->addListener('foo', $listener = function () {});
$listeners = $dispatcher->getListeners('foo');
$this->assertCount(1, $listeners);
$this->assertSame($listener, $listeners[0]);
$tdispatcher->removeListener('foo', $listener);
$this->assertCount(0, $dispatcher->getListeners('foo'));
}
public function testGetListeners()
{
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$tdispatcher->addListener('foo', $listener = function () {});
$this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo'));
}
public function testHasListeners()
{
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$this->assertFalse($dispatcher->hasListeners('foo'));
$this->assertFalse($tdispatcher->hasListeners('foo'));
$tdispatcher->addListener('foo', $listener = function () {});
$this->assertTrue($dispatcher->hasListeners('foo'));
$this->assertTrue($tdispatcher->hasListeners('foo'));
}
public function testGetListenerPriority()
{
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$tdispatcher->addListener('foo', function () {}, 123);
$listeners = $dispatcher->getListeners('foo');
$this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
// Verify that priority is preserved when listener is removed and re-added
// in preProcess() and postProcess().
$tdispatcher->dispatch('foo', new Event());
$listeners = $dispatcher->getListeners('foo');
$this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
}
public function testGetListenerPriorityWhileDispatching()
{
$tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$priorityWhileDispatching = null;
$listener = function () use ($tdispatcher, &$priorityWhileDispatching, &$listener) {
$priorityWhileDispatching = $tdispatcher->getListenerPriority('bar', $listener);
};
$tdispatcher->addListener('bar', $listener, 5);
$tdispatcher->dispatch('bar');
$this->assertSame(5, $priorityWhileDispatching);
}
public function testAddRemoveSubscriber()
{
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$subscriber = new EventSubscriber();
$tdispatcher->addSubscriber($subscriber);
$listeners = $dispatcher->getListeners('foo');
$this->assertCount(1, $listeners);
$this->assertSame(array($subscriber, 'call'), $listeners[0]);
$tdispatcher->removeSubscriber($subscriber);
$this->assertCount(0, $dispatcher->getListeners('foo'));
}
public function testGetCalledListeners()
{
$tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$tdispatcher->addListener('foo', function () {}, 5);
$listeners = $tdispatcher->getNotCalledListeners();
$this->assertArrayHasKey('stub', $listeners['foo.closure']);
unset($listeners['foo.closure']['stub']);
$this->assertEquals(array(), $tdispatcher->getCalledListeners());
$this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 5)), $listeners);
$tdispatcher->dispatch('foo');
$listeners = $tdispatcher->getCalledListeners();
$this->assertArrayHasKey('stub', $listeners['foo.closure']);
unset($listeners['foo.closure']['stub']);
$this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 5)), $listeners);
$this->assertEquals(array(), $tdispatcher->getNotCalledListeners());
}
public function testGetCalledListenersNested()
{
$tdispatcher = null;
$dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) {
$tdispatcher = $dispatcher;
$dispatcher->dispatch('bar');
});
$dispatcher->addListener('bar', function (Event $event) {});
$dispatcher->dispatch('foo');
$this->assertSame($dispatcher, $tdispatcher);
$this->assertCount(2, $dispatcher->getCalledListeners());
}
public function testLogger()
{
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
$tdispatcher->addListener('foo', $listener1 = function () {});
$tdispatcher->addListener('foo', $listener2 = function () {});
$logger->expects($this->at(0))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure'));
$logger->expects($this->at(1))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure'));
$tdispatcher->dispatch('foo');
}
public function testLoggerWithStoppedEvent()
{
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
$tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); });
$tdispatcher->addListener('foo', $listener2 = function () {});
$logger->expects($this->at(0))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure'));
$logger->expects($this->at(1))->method('debug')->with('Listener "{listener}" stopped propagation of the event "{event}".', array('event' => 'foo', 'listener' => 'closure'));
$logger->expects($this->at(2))->method('debug')->with('Listener "{listener}" was not called for event "{event}".', array('event' => 'foo', 'listener' => 'closure'));
$tdispatcher->dispatch('foo');
}
public function testDispatchCallListeners()
{
$called = array();
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo1'; }, 10);
$tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo2'; }, 20);
$tdispatcher->dispatch('foo');
$this->assertSame(array('foo2', 'foo1'), $called);
}
public function testDispatchNested()
{
$dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$loop = 1;
$dispatchedEvents = 0;
$dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) {
++$loop;
if (2 == $loop) {
$dispatcher->dispatch('foo');
}
});
$dispatcher->addListener('foo', function () use (&$dispatchedEvents) {
++$dispatchedEvents;
});
$dispatcher->dispatch('foo');
$this->assertSame(2, $dispatchedEvents);
}
public function testDispatchReusedEventNested()
{
$nestedCall = false;
$dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$dispatcher->addListener('foo', function (Event $e) use ($dispatcher) {
$dispatcher->dispatch('bar', $e);
});
$dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) {
$nestedCall = true;
});
$this->assertFalse($nestedCall);
$dispatcher->dispatch('foo');
$this->assertTrue($nestedCall);
}
public function testListenerCanRemoveItselfWhenExecuted()
{
$eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) {
$dispatcher->removeListener('foo', $listener1);
};
$eventDispatcher->addListener('foo', $listener1);
$eventDispatcher->addListener('foo', function () {});
$eventDispatcher->dispatch('foo');
$this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed');
}
}
class EventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array('foo' => 'call');
}
}

View File

@ -0,0 +1,167 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
class RegisterListenersPassTest extends TestCase
{
/**
* Tests that event subscribers not implementing EventSubscriberInterface
* trigger an exception.
*
* @expectedException \InvalidArgumentException
*/
public function testEventSubscriberWithoutInterface()
{
// one service, not implementing any interface
$services = array(
'my_event_subscriber' => array(0 => array()),
);
$definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock();
$definition->expects($this->atLeastOnce())
->method('getClass')
->will($this->returnValue('stdClass'));
$builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock();
$builder->expects($this->any())
->method('hasDefinition')
->will($this->returnValue(true));
// We don't test kernel.event_listener here
$builder->expects($this->atLeastOnce())
->method('findTaggedServiceIds')
->will($this->onConsecutiveCalls(array(), $services));
$builder->expects($this->atLeastOnce())
->method('getDefinition')
->will($this->returnValue($definition));
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($builder);
}
public function testValidEventSubscriber()
{
$services = array(
'my_event_subscriber' => array(0 => array()),
);
$definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock();
$definition->expects($this->atLeastOnce())
->method('getClass')
->will($this->returnValue('Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'));
$builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'findDefinition'))->getMock();
$builder->expects($this->any())
->method('hasDefinition')
->will($this->returnValue(true));
// We don't test kernel.event_listener here
$builder->expects($this->atLeastOnce())
->method('findTaggedServiceIds')
->will($this->onConsecutiveCalls(array(), $services));
$builder->expects($this->atLeastOnce())
->method('getDefinition')
->will($this->returnValue($definition));
$builder->expects($this->atLeastOnce())
->method('findDefinition')
->will($this->returnValue($definition));
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($builder);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The service "foo" tagged "kernel.event_listener" must not be abstract.
*/
public function testAbstractEventListener()
{
$container = new ContainerBuilder();
$container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array());
$container->register('event_dispatcher', 'stdClass');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The service "foo" tagged "kernel.event_subscriber" must not be abstract.
*/
public function testAbstractEventSubscriber()
{
$container = new ContainerBuilder();
$container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array());
$container->register('event_dispatcher', 'stdClass');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
public function testEventSubscriberResolvableClassName()
{
$container = new ContainerBuilder();
$container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService');
$container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
$container->register('event_dispatcher', 'stdClass');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = array(
array(
'addListener',
array(
'event',
array(new ServiceClosureArgument(new Reference('foo')), 'onEvent'),
0,
),
),
);
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class"
*/
public function testEventSubscriberUnresolvableClassName()
{
$container = new ContainerBuilder();
$container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
$container->register('event_dispatcher', 'stdClass');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
}
class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
'event' => 'onEvent',
);
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use Symfony\Component\EventDispatcher\EventDispatcher;
class EventDispatcherTest extends AbstractEventDispatcherTest
{
protected function createEventDispatcher()
{
return new EventDispatcher();
}
}

View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\Event;
/**
* Test class for Event.
*/
class EventTest extends TestCase
{
/**
* @var \Symfony\Component\EventDispatcher\Event
*/
protected $event;
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->event = new Event();
}
/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
$this->event = null;
}
public function testIsPropagationStopped()
{
$this->assertFalse($this->event->isPropagationStopped());
}
public function testStopPropagationAndIsPropagationStopped()
{
$this->event->stopPropagation();
$this->assertTrue($this->event->isPropagationStopped());
}
}

View File

@ -0,0 +1,140 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Test class for Event.
*/
class GenericEventTest extends TestCase
{
/**
* @var GenericEvent
*/
private $event;
private $subject;
/**
* Prepares the environment before running a test.
*/
protected function setUp()
{
parent::setUp();
$this->subject = new \stdClass();
$this->event = new GenericEvent($this->subject, array('name' => 'Event'));
}
/**
* Cleans up the environment after running a test.
*/
protected function tearDown()
{
$this->subject = null;
$this->event = null;
parent::tearDown();
}
public function testConstruct()
{
$this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event')));
}
/**
* Tests Event->getArgs().
*/
public function testGetArguments()
{
// test getting all
$this->assertSame(array('name' => 'Event'), $this->event->getArguments());
}
public function testSetArguments()
{
$result = $this->event->setArguments(array('foo' => 'bar'));
$this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event);
$this->assertSame($this->event, $result);
}
public function testSetArgument()
{
$result = $this->event->setArgument('foo2', 'bar2');
$this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
$this->assertEquals($this->event, $result);
}
public function testGetArgument()
{
// test getting key
$this->assertEquals('Event', $this->event->getArgument('name'));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testGetArgException()
{
$this->event->getArgument('nameNotExist');
}
public function testOffsetGet()
{
// test getting key
$this->assertEquals('Event', $this->event['name']);
// test getting invalid arg
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
$this->assertFalse($this->event['nameNotExist']);
}
public function testOffsetSet()
{
$this->event['foo2'] = 'bar2';
$this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
}
public function testOffsetUnset()
{
unset($this->event['name']);
$this->assertAttributeSame(array(), 'arguments', $this->event);
}
public function testOffsetIsset()
{
$this->assertTrue(isset($this->event['name']));
$this->assertFalse(isset($this->event['nameNotExist']));
}
public function testHasArgument()
{
$this->assertTrue($this->event->hasArgument('name'));
$this->assertFalse($this->event->hasArgument('nameNotExist'));
}
public function testGetSubject()
{
$this->assertSame($this->subject, $this->event->getSubject());
}
public function testHasIterator()
{
$data = array();
foreach ($this->event as $key => $value) {
$data[$key] = $value;
}
$this->assertEquals(array('name' => 'Event'), $data);
}
}

View File

@ -0,0 +1,106 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ImmutableEventDispatcherTest extends TestCase
{
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $innerDispatcher;
/**
* @var ImmutableEventDispatcher
*/
private $dispatcher;
protected function setUp()
{
$this->innerDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
$this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher);
}
public function testDispatchDelegates()
{
$event = new Event();
$this->innerDispatcher->expects($this->once())
->method('dispatch')
->with('event', $event)
->will($this->returnValue('result'));
$this->assertSame('result', $this->dispatcher->dispatch('event', $event));
}
public function testGetListenersDelegates()
{
$this->innerDispatcher->expects($this->once())
->method('getListeners')
->with('event')
->will($this->returnValue('result'));
$this->assertSame('result', $this->dispatcher->getListeners('event'));
}
public function testHasListenersDelegates()
{
$this->innerDispatcher->expects($this->once())
->method('hasListeners')
->with('event')
->will($this->returnValue('result'));
$this->assertSame('result', $this->dispatcher->hasListeners('event'));
}
/**
* @expectedException \BadMethodCallException
*/
public function testAddListenerDisallowed()
{
$this->dispatcher->addListener('event', function () { return 'foo'; });
}
/**
* @expectedException \BadMethodCallException
*/
public function testAddSubscriberDisallowed()
{
$subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
$this->dispatcher->addSubscriber($subscriber);
}
/**
* @expectedException \BadMethodCallException
*/
public function testRemoveListenerDisallowed()
{
$this->dispatcher->removeListener('event', function () { return 'foo'; });
}
/**
* @expectedException \BadMethodCallException
*/
public function testRemoveSubscriberDisallowed()
{
$subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
$this->dispatcher->removeSubscriber($subscriber);
}
}

View File

@ -0,0 +1,47 @@
{
"name": "symfony/event-dispatcher",
"type": "library",
"description": "Symfony EventDispatcher Component",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=5.5.9"
},
"require-dev": {
"symfony/dependency-injection": "~3.3",
"symfony/expression-language": "~2.8|~3.0",
"symfony/config": "~2.8|~3.0",
"symfony/stopwatch": "~2.8|~3.0",
"psr/log": "~1.0"
},
"conflict": {
"symfony/dependency-injection": "<3.3"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"autoload": {
"psr-4": { "Symfony\\Component\\EventDispatcher\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
}
}

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony EventDispatcher Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Resources</directory>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>