new chart chartjs pandora_enterprise#9554
This commit is contained in:
parent
1ebe09698b
commit
01d14ae3b8
|
@ -9,13 +9,15 @@
|
|||
],
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.2.0"
|
||||
"php": "8.0.0"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"mpdf/mpdf": "^8.0.15",
|
||||
"swiftmailer/swiftmailer": "^6.0",
|
||||
"amphp/parallel-functions": "^1.0"
|
||||
"amphp/parallel-functions": "^1.0",
|
||||
"nutsy/phpchartjs": "*",
|
||||
"chrome-php/chrome": "^1.7.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "643ac0dc8a8e1f129104399054f8dd0c",
|
||||
"content-hash": "8df19e49d9715736491944daea32b45c",
|
||||
"packages": [
|
||||
{
|
||||
"name": "amphp/amp",
|
||||
|
@ -549,6 +549,142 @@
|
|||
],
|
||||
"time": "2021-10-25T18:29:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "chrome-php/chrome",
|
||||
"version": "v1.7.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/chrome-php/chrome.git",
|
||||
"reference": "5783c749b2ee385d1c481b0906f1b8acef0296e4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/chrome-php/chrome/zipball/5783c749b2ee385d1c481b0906f1b8acef0296e4",
|
||||
"reference": "5783c749b2ee385d1c481b0906f1b8acef0296e4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"chrome-php/wrench": "^1.3",
|
||||
"evenement/evenement": "^3.0.1",
|
||||
"monolog/monolog": "^1.27.1 || ^2.8 || ^3.2",
|
||||
"php": "^7.3 || ^8.0",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0",
|
||||
"symfony/filesystem": "^4.4 || ^5.0 || ^6.0",
|
||||
"symfony/polyfill-mbstring": "^1.26",
|
||||
"symfony/process": "^4.4 || ^5.0 || ^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
||||
"phpunit/phpunit": "^9.5.23",
|
||||
"symfony/var-dumper": "^4.4 || ^5.0 || ^6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"HeadlessChromium\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Enrico Dias",
|
||||
"email": "enrico@enricodias.com",
|
||||
"homepage": "https://github.com/enricodias"
|
||||
}
|
||||
],
|
||||
"description": "Instrument headless chrome/chromium instances from PHP",
|
||||
"keywords": [
|
||||
"browser",
|
||||
"chrome",
|
||||
"chromium",
|
||||
"crawl",
|
||||
"headless",
|
||||
"pdf",
|
||||
"puppeteer",
|
||||
"screenshot"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/chrome-php/chrome/issues",
|
||||
"source": "https://github.com/chrome-php/chrome/tree/v1.7.1"
|
||||
},
|
||||
"time": "2022-09-04T21:11:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "chrome-php/wrench",
|
||||
"version": "v1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/chrome-php/wrench.git",
|
||||
"reference": "68b8282d5d0d54a519c3212ee3e4c35bef40b7d9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/chrome-php/wrench/zipball/68b8282d5d0d54a519c3212ee3e4c35bef40b7d9",
|
||||
"reference": "68b8282d5d0d54a519c3212ee3e4c35bef40b7d9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-sockets": "*",
|
||||
"php": "^7.3 || ^8.0",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0",
|
||||
"symfony/polyfill-php80": "^1.26"
|
||||
},
|
||||
"conflict": {
|
||||
"wrench/wrench": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
||||
"phpunit/phpunit": "^9.5.23"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Wrench\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "A simple PHP WebSocket implementation",
|
||||
"keywords": [
|
||||
"WebSockets",
|
||||
"hybi",
|
||||
"websocket"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/chrome-php/wrench/issues",
|
||||
"source": "https://github.com/chrome-php/wrench/tree/v1.3.0"
|
||||
},
|
||||
"time": "2022-08-28T11:42:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/lexer",
|
||||
"version": "1.2.2",
|
||||
|
@ -693,6 +829,265 @@
|
|||
],
|
||||
"time": "2021-10-11T09:18:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "evenement/evenement",
|
||||
"version": "v3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/igorw/evenement.git",
|
||||
"reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
|
||||
"reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Evenement": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Igor Wiedler",
|
||||
"email": "igor@wiedler.ch"
|
||||
}
|
||||
],
|
||||
"description": "Événement is a very simple event dispatching library for PHP",
|
||||
"keywords": [
|
||||
"event-dispatcher",
|
||||
"event-emitter"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/igorw/evenement/issues",
|
||||
"source": "https://github.com/igorw/evenement/tree/master"
|
||||
},
|
||||
"time": "2017-07-23T21:35:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "halfpastfouram/collection",
|
||||
"version": "v1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/halfpastfouram/collection.git",
|
||||
"reference": "0862d0b431fef9dc2245518dc06b86ff00dcd102"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/halfpastfouram/collection/zipball/0862d0b431fef9dc2245518dc06b86ff00dcd102",
|
||||
"reference": "0862d0b431fef9dc2245518dc06b86ff00dcd102",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6.0 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeclimate/php-test-reporter": "dev-master",
|
||||
"phpunit/phpunit": "5.2.*"
|
||||
},
|
||||
"type": "package",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Test\\": "test/",
|
||||
"Halfpastfour\\Collection\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"AGPL 3.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bob Kruithof"
|
||||
}
|
||||
],
|
||||
"description": "A flexible PHP Collection complete with custom Iterator.",
|
||||
"homepage": "http://github.com/halfpastfouram/collection",
|
||||
"keywords": [
|
||||
"collection",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/halfpastfouram/collection/issues",
|
||||
"source": "https://github.com/halfpastfouram/collection/tree/master"
|
||||
},
|
||||
"time": "2016-12-18T13:04:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laminas/laminas-json",
|
||||
"version": "3.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laminas/laminas-json.git",
|
||||
"reference": "7a8a1d7bf2d05dd6c1fbd7c0868d3848cf2b57ec"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laminas/laminas-json/zipball/7a8a1d7bf2d05dd6c1fbd7c0868d3848cf2b57ec",
|
||||
"reference": "7a8a1d7bf2d05dd6c1fbd7c0868d3848cf2b57ec",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~8.0.0 || ~8.1.0 || ~8.2.0"
|
||||
},
|
||||
"conflict": {
|
||||
"zendframework/zend-json": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laminas/laminas-coding-standard": "~2.4.0",
|
||||
"laminas/laminas-stdlib": "^2.7.7 || ^3.1",
|
||||
"phpunit/phpunit": "^9.5.25"
|
||||
},
|
||||
"suggest": {
|
||||
"laminas/laminas-json-server": "For implementing JSON-RPC servers",
|
||||
"laminas/laminas-xml2json": "For converting XML documents to JSON"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laminas\\Json\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP",
|
||||
"homepage": "https://laminas.dev",
|
||||
"keywords": [
|
||||
"json",
|
||||
"laminas"
|
||||
],
|
||||
"support": {
|
||||
"chat": "https://laminas.dev/chat",
|
||||
"docs": "https://docs.laminas.dev/laminas-json/",
|
||||
"forum": "https://discourse.laminas.dev",
|
||||
"issues": "https://github.com/laminas/laminas-json/issues",
|
||||
"rss": "https://github.com/laminas/laminas-json/releases.atom",
|
||||
"source": "https://github.com/laminas/laminas-json"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://funding.communitybridge.org/projects/laminas-project",
|
||||
"type": "community_bridge"
|
||||
}
|
||||
],
|
||||
"time": "2022-10-17T04:06:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "2.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Seldaek/monolog.git",
|
||||
"reference": "720488632c590286b88b80e62aa3d3d551ad4a50"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/720488632c590286b88b80e62aa3d3d551ad4a50",
|
||||
"reference": "720488632c590286b88b80e62aa3d3d551ad4a50",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
|
||||
"doctrine/couchdb": "~1.0@dev",
|
||||
"elasticsearch/elasticsearch": "^7 || ^8",
|
||||
"ext-json": "*",
|
||||
"graylog2/gelf-php": "^1.4.2",
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
"guzzlehttp/psr7": "^2.2",
|
||||
"mongodb/mongodb": "^1.8",
|
||||
"php-amqplib/php-amqplib": "~2.4 || ^3",
|
||||
"phpspec/prophecy": "^1.15",
|
||||
"phpstan/phpstan": "^0.12.91",
|
||||
"phpunit/phpunit": "^8.5.14",
|
||||
"predis/predis": "^1.1 || ^2.0",
|
||||
"rollbar/rollbar": "^1.3 || ^2 || ^3",
|
||||
"ruflin/elastica": "^7",
|
||||
"swiftmailer/swiftmailer": "^5.3|^6.0",
|
||||
"symfony/mailer": "^5.4 || ^6",
|
||||
"symfony/mime": "^5.4 || ^6"
|
||||
},
|
||||
"suggest": {
|
||||
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
|
||||
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
|
||||
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
|
||||
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
|
||||
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
|
||||
"ext-mbstring": "Allow to work properly with unicode symbols",
|
||||
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
|
||||
"ext-openssl": "Required to send log messages using SSL",
|
||||
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
|
||||
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
|
||||
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
|
||||
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
|
||||
"rollbar/rollbar": "Allow sending log messages to Rollbar",
|
||||
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Monolog\\": "src/Monolog"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "https://seld.be"
|
||||
}
|
||||
],
|
||||
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
|
||||
"homepage": "https://github.com/Seldaek/monolog",
|
||||
"keywords": [
|
||||
"log",
|
||||
"logging",
|
||||
"psr-3"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Seldaek/monolog/issues",
|
||||
"source": "https://github.com/Seldaek/monolog/tree/2.8.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Seldaek",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-07-24T11:55:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mpdf/mpdf",
|
||||
"version": "v8.0.15",
|
||||
|
@ -823,6 +1218,62 @@
|
|||
],
|
||||
"time": "2020-11-13T09:40:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nutsy/phpchartjs",
|
||||
"version": "v1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/danielbarmar85/phpchartjs.git",
|
||||
"reference": "a6c7683198e6c4d4536fd8063a38e6277598a1b8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/danielbarmar85/phpchartjs/zipball/a6c7683198e6c4d4536fd8063a38e6277598a1b8",
|
||||
"reference": "a6c7683198e6c4d4536fd8063a38e6277598a1b8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-json": "*",
|
||||
"halfpastfouram/collection": "1.0.0",
|
||||
"laminas/laminas-json": ">3.1.2",
|
||||
"php": ">=7.2",
|
||||
"symfony/var-dumper": "^3.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "*",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"sensiolabs/security-checker": "^5.0",
|
||||
"squizlabs/php_codesniffer": "3.5.3"
|
||||
},
|
||||
"type": "package",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Test\\": "test/",
|
||||
"Nutsy\\PHPChartJS\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"AGPL-3.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniel Barbero"
|
||||
}
|
||||
],
|
||||
"description": "PHP library for ChartJS",
|
||||
"homepage": "https://thenutsycompany.com/",
|
||||
"keywords": [
|
||||
"chartjs",
|
||||
"graph",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/danielbarmar85/phpchartjs/tree/v1.0.0"
|
||||
},
|
||||
"time": "2022-11-23T13:00:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "opis/closure",
|
||||
"version": "3.6.3",
|
||||
|
@ -940,30 +1391,30 @@
|
|||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "1.1.4",
|
||||
"version": "2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "d49695b909c3b7628b6289db5479a1c204601f11"
|
||||
"reference": "ef29f6d262798707a9edd554e2b82517ef3a9376"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
|
||||
"reference": "d49695b909c3b7628b6289db5479a1c204601f11",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376",
|
||||
"reference": "ef29f6d262798707a9edd554e2b82517ef3a9376",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
"php": ">=8.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Log\\": "Psr/Log/"
|
||||
"Psr\\Log\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
|
@ -984,9 +1435,9 @@
|
|||
"psr-3"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/log/tree/1.1.4"
|
||||
"source": "https://github.com/php-fig/log/tree/2.0.0"
|
||||
},
|
||||
"time": "2021-05-03T11:20:27+00:00"
|
||||
"time": "2021-07-14T16:41:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "setasign/fpdi",
|
||||
|
@ -1136,6 +1587,152 @@
|
|||
"abandoned": "symfony/mailer",
|
||||
"time": "2021-10-18T15:26:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v5.4.13",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "ac09569844a9109a5966b9438fc29113ce77cf51"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/ac09569844a9109a5966b9438fc29113ce77cf51",
|
||||
"reference": "ac09569844a9109a5966b9438fc29113ce77cf51",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/polyfill-ctype": "~1.8",
|
||||
"symfony/polyfill-mbstring": "~1.8",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Filesystem\\": ""
|
||||
},
|
||||
"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": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v5.4.13"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-09-21T19:53:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
|
||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-iconv",
|
||||
"version": "v1.24.0",
|
||||
|
@ -1392,16 +1989,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.24.0",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
|
||||
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
|
||||
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
|
||||
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1416,7 +2013,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.23-dev"
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
|
@ -1424,12 +2021,12 @@
|
|||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
|
@ -1455,7 +2052,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1471,7 +2068,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-11-30T18:21:41+00:00"
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php72",
|
||||
|
@ -1548,6 +2145,232 @@
|
|||
}
|
||||
],
|
||||
"time": "2021-05-27T09:17:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
|
||||
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php80\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ion Bazan",
|
||||
"email": "ion.bazan@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v5.4.11",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1",
|
||||
"reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Process\\": ""
|
||||
},
|
||||
"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": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v5.4.11"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-06-27T16:58:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v3.4.47",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
"reference": "0719f6cf4633a38b2c1585140998579ce23b4b7d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/0719f6cf4633a38b2c1585140998579ce23b4b7d",
|
||||
"reference": "0719f6cf4633a38b2c1585140998579ce23b4b7d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.5.9|>=7.0.8",
|
||||
"symfony/polyfill-mbstring": "~1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-iconv": "*",
|
||||
"twig/twig": "~1.34|~2.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
|
||||
"ext-intl": "To show region name in time zone dump",
|
||||
"ext-symfony_debug": ""
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"Resources/functions/dump.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\VarDumper\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony mechanism for exploring and dumping PHP variables",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"debug",
|
||||
"dump"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v3.4.47"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-10-24T10:57:07+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
@ -1559,7 +2382,7 @@
|
|||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"platform-overrides": {
|
||||
"php": "7.2.0"
|
||||
"php": "8.0.0"
|
||||
},
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
|
|
@ -2018,8 +2018,10 @@ if ($total_event_graph) {
|
|||
|
||||
include_once $config['homedir'].'/include/functions_graph.php';
|
||||
|
||||
$prueba = grafico_eventos_total('', 280, 150, false, true);
|
||||
echo $prueba;
|
||||
$out = '<div style="flex: 0 0 300px; width:99%; height:100%;">';
|
||||
$out .= grafico_eventos_total('', 0, 0, false, true);
|
||||
$out .= '<div>';
|
||||
echo $out;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2028,8 +2030,10 @@ if ($graphic_event_group) {
|
|||
|
||||
include_once $config['homedir'].'/include/functions_graph.php';
|
||||
|
||||
$prueba = grafico_eventos_grupo(280, 150, '', false, true);
|
||||
echo $prueba;
|
||||
$out = '<div style="flex: 0 0 300px; width:99%; height:100%;">';
|
||||
$out .= grafico_eventos_grupo(0, 0, '', false, true);
|
||||
$out .= '<div>';
|
||||
echo $out;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,40 +42,15 @@ require_once $config['homedir'].'/include/functions_agents.php';
|
|||
require_once $config['homedir'].'/include/functions_tags.php';
|
||||
|
||||
$data_raw = get_parameter('data');
|
||||
$data_decoded = json_decode(base64_decode($data_raw), true);
|
||||
$data_decoded = json_decode(io_safe_output($data_raw), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$data = urldecode($data_decoded['data']);
|
||||
$session_id = urldecode($data_decoded['session_id']);
|
||||
$data_combined = urldecode($data_decoded['data_combined']);
|
||||
$data_module_list = urldecode($data_decoded['data_module_list']);
|
||||
$type_graph_pdf = urldecode($data_decoded['type_graph_pdf']);
|
||||
$viewport_width = urldecode($data_decoded['viewport_width']);
|
||||
$data = $data_decoded['data'];
|
||||
$session_id = $data_decoded['session_id'];
|
||||
$data_combined = $data_decoded['data_combined'];
|
||||
$data_module_list = $data_decoded['data_module_list'];
|
||||
$type_graph_pdf = $data_decoded['type_graph_pdf'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Echo to stdout a PhantomJS callback call.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function echoPhantomCallback()
|
||||
{
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
$('document').ready(function () {
|
||||
setTimeout(function () {
|
||||
try {
|
||||
var status = window.callPhantom({ status: "loaded" });
|
||||
} catch (error) {
|
||||
console.log("CALLBACK ERROR", error.message)
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
// Initialize session.
|
||||
global $config;
|
||||
|
||||
|
@ -99,7 +74,6 @@ if (check_login(false) === false) {
|
|||
</head>
|
||||
<body>
|
||||
<h1>Access is not granted</h1>
|
||||
<?php echoPhantomCallback(); ?>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -108,7 +82,7 @@ if (check_login(false) === false) {
|
|||
}
|
||||
|
||||
// Access granted.
|
||||
$params = json_decode($data, true);
|
||||
$params = $data;
|
||||
|
||||
// Metaconsole connection to the node.
|
||||
$server_id = $params['server_id'];
|
||||
|
@ -124,7 +98,6 @@ if (is_metaconsole() === true && empty($server_id) === false) {
|
|||
ui_print_error_message(
|
||||
__('There was a problem connecting with the node')
|
||||
);
|
||||
echoPhantomCallback();
|
||||
?>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -133,7 +106,6 @@ if (is_metaconsole() === true && empty($server_id) === false) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
$user_language = get_user_language($config['id_user']);
|
||||
if (file_exists('languages/'.$user_language.'.mo') === true) {
|
||||
$cfr = new CachedFileReader('languages/'.$user_language.'.mo');
|
||||
|
@ -146,16 +118,16 @@ if (file_exists('languages/'.$user_language.'.mo') === true) {
|
|||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>Pandora FMS Graph (<?php echo agents_get_alias($agent_id).' - '.$interface_name; ?>)</title>
|
||||
<title>Pandora FMS Graph</title>
|
||||
<link rel="stylesheet" href="styles/pandora.css" type="text/css" />
|
||||
<link rel="stylesheet" href="styles/pandora_minimal.css" type="text/css" />
|
||||
<link rel="stylesheet" href="styles/js/jquery-ui.min.css" type="text/css" />
|
||||
<link rel="stylesheet" href="styles/js/jquery-ui_custom.css" type="text/css" />
|
||||
<script language="javascript" type='text/javascript' src='javascript/pandora.js'></script>
|
||||
<script language="javascript" type='text/javascript' src='javascript/pandora_ui.js'></script>
|
||||
<script language="javascript" type='text/javascript' src='javascript/jquery.current.js'></script>
|
||||
<script language="javascript" type='text/javascript' src='javascript/jquery.pandora.js'></script>
|
||||
<script language="javascript" type='text/javascript' src='javascript/jquery-ui.min.js'></script>
|
||||
<script language="javascript" type='text/javascript' src='javascript/pandora.js'></script>
|
||||
<script language="javascript" type="text/javascript" src="graphs/flot/jquery.flot.js"></script>
|
||||
<script language="javascript" type="text/javascript" src="graphs/flot/jquery.flot.min.js"></script>
|
||||
<script language="javascript" type="text/javascript" src="graphs/flot/jquery.flot.time.js"></script>
|
||||
|
@ -170,6 +142,7 @@ if (file_exists('languages/'.$user_language.'.mo') === true) {
|
|||
<script language="javascript" type="text/javascript" src="graphs/flot/jquery.flot.exportdata.pandora.js"></script>
|
||||
<script language="javascript" type="text/javascript" src="graphs/flot/jquery.flot.axislabels.js"></script>
|
||||
<script language="javascript" type="text/javascript" src="graphs/flot/pandora.flot.js"></script>
|
||||
<script language="javascript" type="text/javascript" src="graphs/chartjs/chart.js"></script>
|
||||
</head>
|
||||
<body style='background-color: <?php echo $params['backgroundColor']; ?>;'>
|
||||
<?php
|
||||
|
@ -177,41 +150,38 @@ if (file_exists('languages/'.$user_language.'.mo') === true) {
|
|||
$params['menu'] = false;
|
||||
|
||||
$params['disable_black'] = true;
|
||||
$params_combined = json_decode($data_combined, true);
|
||||
$module_list = json_decode($data_module_list, true);
|
||||
$params_combined = $data_combined;
|
||||
$module_list = $data_module_list;
|
||||
|
||||
if (isset($params['vconsole']) === false || $params['vconsole'] === false) {
|
||||
if ((int) $viewport_width > 0) {
|
||||
$params['width'] = (int) $viewport_width;
|
||||
}
|
||||
$viewport = [
|
||||
'width' => 0,
|
||||
'height' => 0,
|
||||
];
|
||||
|
||||
if ((isset($params['width']) === false
|
||||
|| ($params['width'] <= 0))
|
||||
) {
|
||||
if ((int) $params['width'] <= 0) {
|
||||
$params['width'] = 650;
|
||||
}
|
||||
|
||||
if ((int) $params['landscape'] === 1) {
|
||||
$params['width'] = 850;
|
||||
}
|
||||
|
||||
if ($type_graph_pdf === 'slicebar') {
|
||||
$params['width'] = 100;
|
||||
$params['height'] = 70;
|
||||
}
|
||||
}
|
||||
if (isset($params['options']['viewport']) === true) {
|
||||
$viewport = $params['viewport'];
|
||||
}
|
||||
|
||||
echo '<div>';
|
||||
$style = '';
|
||||
if (empty($params['options']['viewport']['width']) === false) {
|
||||
$style .= 'width:'.$params['options']['viewport']['width'].'px;';
|
||||
}
|
||||
|
||||
if (empty($params['options']['viewport']['height']) === false) {
|
||||
$style .= 'height:'.$params['options']['viewport']['height'].'px;';
|
||||
}
|
||||
|
||||
echo '<div id="container-chart-generator-item" style="'.$style.' margin:0px;">';
|
||||
switch ($type_graph_pdf) {
|
||||
case 'combined':
|
||||
$params['pdf'] = true;
|
||||
echo graphic_combined_module(
|
||||
$result = graphic_combined_module(
|
||||
$module_list,
|
||||
$params,
|
||||
$params_combined
|
||||
);
|
||||
|
||||
echo $result;
|
||||
break;
|
||||
|
||||
case 'sparse':
|
||||
|
@ -219,20 +189,15 @@ if (file_exists('languages/'.$user_language.'.mo') === true) {
|
|||
echo grafico_modulo_sparse($params);
|
||||
break;
|
||||
|
||||
case 'pie_chart':
|
||||
case 'pie_graph':
|
||||
$params['pdf'] = true;
|
||||
echo flot_pie_chart(
|
||||
$params['values'],
|
||||
$params['keys'],
|
||||
$params['width'],
|
||||
$params['height'],
|
||||
$params['water_mark_url'],
|
||||
$params['font'],
|
||||
$config['font_size'],
|
||||
$params['legend_position'],
|
||||
$params['colors'],
|
||||
$params['hide_labels']
|
||||
$chart = get_build_setup_charts(
|
||||
'PIE',
|
||||
$params['options'],
|
||||
$params['chart_data']
|
||||
);
|
||||
|
||||
echo $chart->render(true);
|
||||
break;
|
||||
|
||||
case 'vbar':
|
||||
|
@ -259,27 +224,16 @@ if (file_exists('languages/'.$user_language.'.mo') === true) {
|
|||
|
||||
case 'ring_graph':
|
||||
$params['pdf'] = true;
|
||||
echo flot_custom_pie_chart(
|
||||
$params['chart_data'],
|
||||
$params['width'],
|
||||
$params['height'],
|
||||
$params['colors'],
|
||||
$params['module_name_list'],
|
||||
$params['long_index'],
|
||||
$params['no_data'],
|
||||
false,
|
||||
'',
|
||||
$params['water_mark'],
|
||||
$params['font'],
|
||||
$config['font_size'],
|
||||
$params['unit'],
|
||||
$params['ttl'],
|
||||
$params['homeurl'],
|
||||
$params['background_color'],
|
||||
$params['legend_position'],
|
||||
$params['background_color'],
|
||||
$params['pdf']
|
||||
$params['options']['width'] = 500;
|
||||
$params['options']['height'] = 500;
|
||||
|
||||
$chart = get_build_setup_charts(
|
||||
'DOUGHNUT',
|
||||
$params['options'],
|
||||
$params['chart_data']
|
||||
);
|
||||
|
||||
echo $chart->render(true);
|
||||
break;
|
||||
|
||||
case 'slicebar':
|
||||
|
@ -313,7 +267,6 @@ if (file_exists('languages/'.$user_language.'.mo') === true) {
|
|||
}
|
||||
|
||||
echo '</div>';
|
||||
echoPhantomCallback();
|
||||
?>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -16,7 +16,11 @@
|
|||
* @subpackage Generic_Functions
|
||||
*/
|
||||
|
||||
/**
|
||||
use HeadlessChromium\BrowserFactory;
|
||||
use HeadlessChromium\Clip;
|
||||
use HeadlessChromium\Page;
|
||||
|
||||
/*
|
||||
* Include the html and ui functions.
|
||||
*/
|
||||
require_once 'functions_html.php';
|
||||
|
@ -4209,8 +4213,7 @@ function generator_chart_to_pdf(
|
|||
$hack_metaconsole = '';
|
||||
}
|
||||
|
||||
$file_js = $config['homedir'].'/include/web2image.js';
|
||||
$url = ui_get_full_url(false).$hack_metaconsole.'/include/chart_generator.php';
|
||||
$url = ui_get_full_url(false).$hack_metaconsole.'/include/chart_generator.php';
|
||||
|
||||
if (!$params['return_img_base_64']) {
|
||||
$img_file = 'img_'.uniqid().'.png';
|
||||
|
@ -4218,65 +4221,65 @@ function generator_chart_to_pdf(
|
|||
$img_url = ui_get_full_url(false).$hack_metaconsole.'/attachment/'.$img_file;
|
||||
}
|
||||
|
||||
if ($type_graph_pdf === 'vbar') {
|
||||
$width_img = $params['generals']['pdf']['width'];
|
||||
$height_img = $params['generals']['pdf']['height'];
|
||||
} else if ($type_graph_pdf === 'combined'
|
||||
&& $params_combined['stacked'] == CUSTOM_GRAPH_VBARS
|
||||
) {
|
||||
$width_img = 650;
|
||||
$height_img = ($params['height'] + 50);
|
||||
} else if ($type_graph_pdf === 'hbar' || $type_graph_pdf === 'pie_chart') {
|
||||
$width_img = ($params['width'] ?? 550);
|
||||
$height_img = $params['height'];
|
||||
} else {
|
||||
$width_img = 550;
|
||||
$height_img = $params['height'];
|
||||
|
||||
if ((int) $params['landscape'] === 1) {
|
||||
$height_img = 150;
|
||||
$params['height'] = 150;
|
||||
}
|
||||
}
|
||||
|
||||
$params_encode_json = urlencode(json_encode($params));
|
||||
|
||||
if ($params_combined) {
|
||||
$params_combined = urlencode(json_encode($params_combined));
|
||||
}
|
||||
|
||||
if ($module_list) {
|
||||
$module_list = urlencode(json_encode($module_list));
|
||||
}
|
||||
|
||||
$session_id = session_id();
|
||||
$cache_dir = $config['homedir'].'/attachment/cache';
|
||||
if ($type_graph_pdf === 'combined') {
|
||||
$data = [
|
||||
'data' => $params,
|
||||
'session_id' => $session_id,
|
||||
'type_graph_pdf' => $type_graph_pdf,
|
||||
'data_module_list' => $module_list,
|
||||
'data_combined' => $params_combined,
|
||||
];
|
||||
} else {
|
||||
$data = [
|
||||
'data' => $params,
|
||||
'session_id' => $session_id,
|
||||
'type_graph_pdf' => $type_graph_pdf,
|
||||
];
|
||||
}
|
||||
|
||||
$cmd = '"'.io_safe_output($config['phantomjs_bin']);
|
||||
$cmd .= DIRECTORY_SEPARATOR.'phantomjs" ';
|
||||
$cmd .= ' --disk-cache=true --disk-cache-path="'.$cache_dir.'"';
|
||||
$cmd .= ' --max-disk-cache-size=10000 ';
|
||||
$cmd .= ' --ssl-protocol=any --ignore-ssl-errors=true ';
|
||||
$cmd .= '"'.$file_js.'" "'.$url.'" "'.$type_graph_pdf.'"';
|
||||
$cmd .= ' "'.$params_encode_json.'" "'.$params_combined.'"';
|
||||
$cmd .= ' "'.$module_list.'" "'.$img_path.'"';
|
||||
$cmd .= ' "'.$width_img.'" "'.$height_img.'"';
|
||||
$cmd .= ' "'.$session_id.'" "'.$params['return_img_base_64'].'"';
|
||||
$browserFactory = new BrowserFactory('chromium-browser');
|
||||
|
||||
$result = null;
|
||||
$retcode = null;
|
||||
exec($cmd, $result, $retcode);
|
||||
// Starts headless chrome.
|
||||
$browser = $browserFactory->createBrowser(['noSandbox' => true]);
|
||||
try {
|
||||
// Creates a new page and navigate to an URL.
|
||||
$page = $browser->createPage();
|
||||
|
||||
$img_content = join("\n", $result);
|
||||
$page->navigate($url.'?data='.urlencode(json_encode($data)))->waitForNavigation(Page::DOM_CONTENT_LOADED);
|
||||
$dynamic_height = $page->evaluate('document.getElementById("container-chart-generator-item").clientHeight')->getReturnValue();
|
||||
if (empty($dynamic_height) === true) {
|
||||
$dynamic_height = 200;
|
||||
}
|
||||
|
||||
if ($params['return_img_base_64']) {
|
||||
$width = 794;
|
||||
if (isset($params['options']['viewport']) === true
|
||||
&& isset($params['options']['viewport']['width']) === true
|
||||
) {
|
||||
$width = $params['options']['viewport']['width'];
|
||||
}
|
||||
|
||||
$clip = new Clip(0, 0, $width, $dynamic_height);
|
||||
$b64 = $page->screenshot(['clip' => $clip])->getBase64();
|
||||
} catch (\Throwable $th) {
|
||||
hd($th, true);
|
||||
} finally {
|
||||
$browser->close();
|
||||
}
|
||||
|
||||
// TODO: XXX chartjs.
|
||||
/*
|
||||
if ($params['return_img_base_64']) {
|
||||
// To be used in alerts.
|
||||
return $img_content;
|
||||
} else {
|
||||
} else {
|
||||
// To be used in PDF files.
|
||||
$config['temp_images'][] = $img_path;
|
||||
return '<img src="'.$img_url.'" />';
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return $b64;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2600,27 +2600,6 @@ function events_print_event_table(
|
|||
$events_table = html_print_table($table, true);
|
||||
$out = $events_table;
|
||||
|
||||
if (!$tactical_view) {
|
||||
$out .= '<table width="100%"><tr><td class="w90p align-top pdd_t_0px">';
|
||||
if ($agent_id != 0) {
|
||||
$out .= '</td><td class="w200px align-top">';
|
||||
$out .= '<table cellpadding=0 cellspacing=0 class="databox"><tr><td>';
|
||||
$out .= '<fieldset class="databox tactical_set">
|
||||
<legend>'.__('Events -by module-').'</legend>'.graph_event_module(180, 100, $event['id_agente']).'</fieldset>';
|
||||
$out .= '</td></tr></table>';
|
||||
} else {
|
||||
$out .= '</td><td class="w200px align-top">';
|
||||
$out .= '<table cellpadding=0 cellspacing=0 class="databox"><tr><td>';
|
||||
$out .= '<fieldset class="databox tactical_set">
|
||||
<legend>'.__('Event graph').'</legend>'.grafico_eventos_total('', 180, 60).'</fieldset>';
|
||||
$out .= '<fieldset class="databox tactical_set">
|
||||
<legend>'.__('Event graph by agent').'</legend>'.grafico_eventos_grupo(180, 60).'</fieldset>';
|
||||
$out .= '</td></tr></table>';
|
||||
}
|
||||
|
||||
$out .= '</td></tr></table>';
|
||||
}
|
||||
|
||||
unset($table);
|
||||
|
||||
if ($return) {
|
||||
|
|
|
@ -814,14 +814,7 @@ function grafico_modulo_sparse($params)
|
|||
}
|
||||
|
||||
if (isset($params['agent_module_id']) === false) {
|
||||
return graph_nodata_image(
|
||||
$params['width'],
|
||||
$params['height'],
|
||||
'area',
|
||||
'',
|
||||
false,
|
||||
$params['pdf']
|
||||
);
|
||||
return graph_nodata_image($params);
|
||||
} else {
|
||||
$agent_module_id = $params['agent_module_id'];
|
||||
}
|
||||
|
@ -1024,14 +1017,7 @@ function grafico_modulo_sparse($params)
|
|||
$array_events_alerts
|
||||
);
|
||||
} else {
|
||||
$return = graph_nodata_image(
|
||||
$params['width'],
|
||||
$params['height'],
|
||||
'area',
|
||||
'',
|
||||
false,
|
||||
$params['pdf']
|
||||
);
|
||||
$return = graph_nodata_image($params);
|
||||
}
|
||||
|
||||
$return .= '<br>';
|
||||
|
@ -1058,14 +1044,7 @@ function grafico_modulo_sparse($params)
|
|||
$array_events_alerts
|
||||
);
|
||||
} else {
|
||||
$return = graph_nodata_image(
|
||||
$params['width'],
|
||||
$params['height'],
|
||||
'area',
|
||||
'',
|
||||
false,
|
||||
$params['pdf']
|
||||
);
|
||||
$return = graph_nodata_image($params);
|
||||
}
|
||||
} else {
|
||||
if (empty($array_data) === false) {
|
||||
|
@ -1082,14 +1061,7 @@ function grafico_modulo_sparse($params)
|
|||
$array_events_alerts
|
||||
);
|
||||
} else {
|
||||
$return = graph_nodata_image(
|
||||
$params['width'],
|
||||
$params['height'],
|
||||
'area',
|
||||
__('No data to display within the selected interval'),
|
||||
false,
|
||||
$params['pdf']
|
||||
);
|
||||
$return = graph_nodata_image($params);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1550,6 +1522,12 @@ function graphic_combined_module(
|
|||
$height = $params['height'];
|
||||
$homeurl = $params['homeurl'];
|
||||
$ttl = $params['ttl'];
|
||||
|
||||
$date_array = [];
|
||||
$date_array['period'] = $params['period'];
|
||||
$date_array['final_date'] = $params['date'];
|
||||
$date_array['start_date'] = ($params['date'] - $params['period']);
|
||||
|
||||
$background_color = $params['backgroundColor'];
|
||||
$datelimit = $date_array['start_date'];
|
||||
$fixed_font_size = $config['font_size'];
|
||||
|
@ -1733,10 +1711,10 @@ function graphic_combined_module(
|
|||
|
||||
if (empty($array_data) === true) {
|
||||
if ($params_combined['return']) {
|
||||
return graph_nodata_image($width, $height);
|
||||
return graph_nodata_image($params);
|
||||
}
|
||||
|
||||
echo graph_nodata_image($width, $height);
|
||||
echo graph_nodata_image($params);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -2409,8 +2387,7 @@ function graphic_combined_module(
|
|||
}
|
||||
}
|
||||
|
||||
$temp['total_modules'] = $total_modules;
|
||||
|
||||
// $temp['total_modules'] = ['value' => $total_modules];
|
||||
$graph_values = $temp;
|
||||
|
||||
if ($params['vconsole'] === false) {
|
||||
|
@ -2420,24 +2397,25 @@ function graphic_combined_module(
|
|||
$water_mark = false;
|
||||
}
|
||||
|
||||
// TODO: XXX chartjs.
|
||||
$color = color_graph_array();
|
||||
$width = null;
|
||||
$height = null;
|
||||
|
||||
$output = ring_graph(
|
||||
$graph_values,
|
||||
$width,
|
||||
$height,
|
||||
$others_str,
|
||||
$homeurl,
|
||||
$water_mark,
|
||||
$config['fontpath'],
|
||||
($config['font_size'] + 1),
|
||||
$ttl,
|
||||
false,
|
||||
$color,
|
||||
false,
|
||||
$background_color,
|
||||
$params['pdf']
|
||||
);
|
||||
$options = [
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'waterMark' => $water_mark,
|
||||
'ttl' => $ttl,
|
||||
'background' => $background_color,
|
||||
'pdf' => $params['pdf'],
|
||||
];
|
||||
|
||||
$output = '<div style="display: flex; flex-direction:row; justify-content: center; align-items: center; align-content: center; width:100%; height:100%;">';
|
||||
$output .= '<div style="flex: 0 0 auto; width:99%; height:100%;">';
|
||||
$output .= ring_graph($graph_values, $options);
|
||||
$output .= '</div>';
|
||||
$output .= '</div>';
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2651,19 +2629,16 @@ function graph_alert_status($defined_alerts, $fired_alerts, $width=300, $height=
|
|||
];
|
||||
}
|
||||
|
||||
$options = [
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'colors' => $colors,
|
||||
'legend' => ['display' => false],
|
||||
];
|
||||
|
||||
$out = pie_graph(
|
||||
$data,
|
||||
$width,
|
||||
$height,
|
||||
__('other'),
|
||||
'',
|
||||
'',
|
||||
$config['fontpath'],
|
||||
$config['font_size'],
|
||||
1,
|
||||
'hidden',
|
||||
$colors,
|
||||
false
|
||||
$options
|
||||
);
|
||||
|
||||
if ($return) {
|
||||
|
@ -2776,30 +2751,23 @@ function graph_agent_status(
|
|||
$data = [];
|
||||
}
|
||||
|
||||
$options = [
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'colors' => array_values($colors),
|
||||
'legend' => ['display' => false],
|
||||
];
|
||||
|
||||
if ($donut_narrow_graph == true) {
|
||||
$data_total = array_sum($data);
|
||||
$out = print_donut_narrow_graph(
|
||||
$colors,
|
||||
$width,
|
||||
$height,
|
||||
$out = ring_graph(
|
||||
$data,
|
||||
$data_total
|
||||
$options
|
||||
);
|
||||
return $out;
|
||||
} else {
|
||||
$out = pie_graph(
|
||||
$data,
|
||||
$width,
|
||||
$height,
|
||||
__('other'),
|
||||
ui_get_full_url(false, false, false, false),
|
||||
'',
|
||||
$config['fontpath'],
|
||||
$config['font_size'],
|
||||
1,
|
||||
'hidden',
|
||||
$colors,
|
||||
0
|
||||
$options
|
||||
);
|
||||
|
||||
if ($return) {
|
||||
|
@ -2885,6 +2853,8 @@ function graph_event_module($width=300, $height=200, $id_agent=null)
|
|||
];
|
||||
}
|
||||
|
||||
hd('aaaaaaaaaaa');
|
||||
|
||||
return pie_graph(
|
||||
$data,
|
||||
$width,
|
||||
|
@ -3411,17 +3381,20 @@ function grafico_eventos_grupo($width=300, $height=200, $url='', $noWaterMark=tr
|
|||
$water_mark = [];
|
||||
}
|
||||
|
||||
$options = [
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'water_mark' => $water_mark,
|
||||
'legend' => [
|
||||
'display' => true,
|
||||
'position' => 'right',
|
||||
'align' => 'center',
|
||||
],
|
||||
];
|
||||
|
||||
return pie_graph(
|
||||
$data,
|
||||
$width,
|
||||
$height,
|
||||
__('Other'),
|
||||
'',
|
||||
$water_mark,
|
||||
$config['fontpath'],
|
||||
$config['font_size'],
|
||||
1,
|
||||
'bottom'
|
||||
$options
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3438,7 +3411,7 @@ function grafico_eventos_total($filter='', $width=320, $height=200, $noWaterMark
|
|||
|
||||
$filter = str_replace('\\', '', $filter);
|
||||
|
||||
// Add tags condition to filter
|
||||
// Add tags condition to filter.
|
||||
$tags_condition = tags_get_acl_tags($config['id_user'], 0, 'ER', 'event_condition', 'AND');
|
||||
$filter .= $tags_condition;
|
||||
if ($time_limit && $config['event_view_hr']) {
|
||||
|
@ -3519,82 +3492,21 @@ function grafico_eventos_total($filter='', $width=320, $height=200, $noWaterMark
|
|||
$water_mark = [];
|
||||
}
|
||||
|
||||
return pie_graph(
|
||||
$data,
|
||||
$width,
|
||||
$height,
|
||||
__('Other'),
|
||||
'',
|
||||
$water_mark,
|
||||
$config['fontpath'],
|
||||
$config['font_size'],
|
||||
1,
|
||||
'bottom',
|
||||
$colors
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print a pie graph with events data of users
|
||||
*
|
||||
* @param integer height pie graph height
|
||||
* @param integer period time period
|
||||
*/
|
||||
function grafico_eventos_usuario($width, $height)
|
||||
{
|
||||
global $config;
|
||||
global $graphic_type;
|
||||
|
||||
$data = [];
|
||||
$max_items = 5;
|
||||
|
||||
$where = '';
|
||||
if (!users_is_admin()) {
|
||||
$where = 'WHERE event_type NOT IN (\'recon_host_detected\', \'system\',\'error\', \'new_agent\', \'configuration_change\')';
|
||||
}
|
||||
|
||||
$sql = sprintf(
|
||||
'SELECT COUNT(id_evento) events, id_usuario
|
||||
FROM tevento %s
|
||||
GROUP BY id_usuario
|
||||
ORDER BY 1 DESC LIMIT %d',
|
||||
$where,
|
||||
$max_items
|
||||
);
|
||||
|
||||
$events = db_get_all_rows_sql($sql);
|
||||
|
||||
if ($events === false) {
|
||||
$events = [];
|
||||
}
|
||||
|
||||
foreach ($events as $event) {
|
||||
if ($event['id_usuario'] == '0') {
|
||||
$data[__('System')] = $event['events'];
|
||||
} else if ($event['id_usuario'] == '') {
|
||||
$data[__('System')] = $event['events'];
|
||||
} else {
|
||||
$data[$event['id_usuario']] = $event['events'];
|
||||
}
|
||||
}
|
||||
|
||||
$water_mark = [
|
||||
'file' => $config['homedir'].'/images/logo_vertical_water.png',
|
||||
'url' => ui_get_full_url('/images/logo_vertical_water.png', false, false, false),
|
||||
$options = [
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'water_mark' => $water_mark,
|
||||
'colors' => array_values($colors),
|
||||
'legend' => [
|
||||
'display' => true,
|
||||
'position' => 'right',
|
||||
'align' => 'center',
|
||||
],
|
||||
];
|
||||
|
||||
return pie_graph(
|
||||
$data,
|
||||
$width,
|
||||
$height,
|
||||
__('Other'),
|
||||
'',
|
||||
$water_mark,
|
||||
$config['fontpath'],
|
||||
$config['font_size'],
|
||||
1,
|
||||
'bottom'
|
||||
$options
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3854,18 +3766,30 @@ function graph_custom_sql_graph(
|
|||
break;
|
||||
|
||||
case 'sql_graph_pie':
|
||||
$options = [
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'waterMark' => $water_mark,
|
||||
'ttl' => $ttl,
|
||||
];
|
||||
|
||||
if ((int) $ttl === 2) {
|
||||
$output .= '<img src="data:image/png;base64,';
|
||||
} else {
|
||||
$output .= '<div style="margin: 0 auto; width:'.$options_charts['width'].'px;">';
|
||||
}
|
||||
|
||||
// Pie.
|
||||
$output .= pie_graph(
|
||||
$data,
|
||||
$width,
|
||||
$height,
|
||||
__('other'),
|
||||
$homeurl,
|
||||
$water_mark,
|
||||
$config['fontpath'],
|
||||
$config['font_size'],
|
||||
$ttl
|
||||
$options
|
||||
);
|
||||
|
||||
if ((int) $ttl === 2) {
|
||||
$output .= '" />';
|
||||
} else {
|
||||
$output .= '</div>';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -4129,8 +4053,7 @@ function graph_graphic_moduleevents(
|
|||
*/
|
||||
function fs_error_image($width=300, $height=110)
|
||||
{
|
||||
global $config;
|
||||
return graph_nodata_image($width, $height, 'area');
|
||||
return graph_nodata_image(['height' => $height]);
|
||||
}
|
||||
|
||||
|
||||
|
@ -5003,40 +4926,52 @@ function graphic_module_events($id_module, $width, $height, $period=0, $homeurl=
|
|||
}
|
||||
|
||||
|
||||
function graph_nodata_image(
|
||||
$width=300,
|
||||
$height=110,
|
||||
$type='area',
|
||||
$text='',
|
||||
$percent=false,
|
||||
$base64=false
|
||||
) {
|
||||
function graph_nodata_image($options)
|
||||
{
|
||||
global $config;
|
||||
if ($base64 === true) {
|
||||
|
||||
$height = 200;
|
||||
if (isset($options['height']) === true
|
||||
&& empty($options['height']) === false
|
||||
) {
|
||||
$height = $options['height'];
|
||||
}
|
||||
|
||||
return html_print_image(
|
||||
'images/image_problem_area.png',
|
||||
true,
|
||||
[
|
||||
'title' => __('No data'),
|
||||
'style' => 'height:'.$height.'px;',
|
||||
]
|
||||
);
|
||||
|
||||
/*
|
||||
if ($base64 === true) {
|
||||
$dataImg = file_get_contents(
|
||||
$config['homedir'].'/images/image_problem_area_150.png'
|
||||
);
|
||||
return base64_encode($dataImg);
|
||||
}
|
||||
}
|
||||
|
||||
$image = ui_get_full_url(
|
||||
$image = ui_get_full_url(
|
||||
'images/image_problem_area.png',
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
);
|
||||
|
||||
$style = 'text-align:center; padding: 30px 0; display:block; font-size:9.5pt;';
|
||||
$text_div = '<div class="nodata_text" style="'.$style.'">';
|
||||
$text_div .= $text;
|
||||
$text_div .= '</div>';
|
||||
$style = 'text-align:center; padding: 30px 0; display:block; font-size:9.5pt;';
|
||||
$text_div = '<div class="nodata_text" style="'.$style.'">';
|
||||
$text_div .= $text;
|
||||
$text_div .= '</div>';
|
||||
|
||||
$style = 'background-size: contain;background-image: url(\''.$image.'\');';
|
||||
$image_div = '<div class="nodata_container" style="'.$style.'"></div>';
|
||||
$style = 'background-size: contain;background-image: url(\''.$image.'\');';
|
||||
$image_div = '<div class="nodata_container" style="'.$style.'"></div>';
|
||||
|
||||
if ($percent === true) {
|
||||
if ($percent === true) {
|
||||
$div = $image_div;
|
||||
} else {
|
||||
} else {
|
||||
if (strpos($width, '%') === false) {
|
||||
$width = 'width: '.$width.'px;';
|
||||
} else {
|
||||
|
@ -5046,9 +4981,10 @@ function graph_nodata_image(
|
|||
$style = $width.' height:'.$height.'px;';
|
||||
$style .= 'margin: 0 auto;';
|
||||
$div = '<div style="'.$style.'">'.$image_div.'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
return $div;
|
||||
return $div;
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1479,7 +1479,7 @@ function netflow_draw_item(
|
|||
}
|
||||
|
||||
if ($output === 'HTML' || $output === 'PDF') {
|
||||
return graph_nodata_image(300, 110, 'data');
|
||||
return graph_nodata_image(['height' => 110]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -844,7 +844,8 @@ function reporting_make_reporting_data(
|
|||
$content,
|
||||
$type,
|
||||
$force_width_chart,
|
||||
$force_height_chart
|
||||
$force_height_chart,
|
||||
$pdf
|
||||
);
|
||||
if ($report_control['total_events'] == 0 && $content['hide_no_data'] == 1) {
|
||||
break;
|
||||
|
@ -2008,7 +2009,8 @@ function reporting_event_report_group(
|
|||
$content,
|
||||
$type='dinamic',
|
||||
$force_width_chart=null,
|
||||
$force_height_chart=null
|
||||
$force_height_chart=null,
|
||||
$pdf=false
|
||||
) {
|
||||
global $config;
|
||||
|
||||
|
@ -2154,6 +2156,23 @@ function reporting_event_report_group(
|
|||
$return['chart']['by_criticity'] = null;
|
||||
$return['chart']['validated_vs_unvalidated'] = null;
|
||||
|
||||
$options_charts = [
|
||||
'width' => 500,
|
||||
'height' => 150,
|
||||
'radius' => null,
|
||||
'viewport' => [
|
||||
'width' => 500,
|
||||
'height' => 0,
|
||||
],
|
||||
'legend' => [
|
||||
'display' => true,
|
||||
'position' => 'right',
|
||||
'align' => 'center',
|
||||
],
|
||||
'pdf' => $pdf,
|
||||
'ttl' => $ttl,
|
||||
];
|
||||
|
||||
if ($event_graph_by_agent) {
|
||||
$data_graph_by_agent = [];
|
||||
if (empty($data) === false) {
|
||||
|
@ -2171,32 +2190,42 @@ function reporting_event_report_group(
|
|||
}
|
||||
}
|
||||
|
||||
$return['chart']['by_agent'] = pie_graph(
|
||||
if ($pdf === true) {
|
||||
$return['chart']['by_agent'] = '<img src="data:image/png;base64,';
|
||||
} else {
|
||||
$return['chart']['by_agent'] = '<div style="margin: 0 auto; width:'.$options_charts['width'].'px;">';
|
||||
}
|
||||
|
||||
$return['chart']['by_agent'] .= pie_graph(
|
||||
$data_graph_by_agent,
|
||||
500,
|
||||
150,
|
||||
__('other'),
|
||||
ui_get_full_url(false, false, false, false),
|
||||
ui_get_full_url(false, false, false, false).'/images/logo_vertical_water.png',
|
||||
$config['fontpath'],
|
||||
$config['font_size'],
|
||||
$ttl
|
||||
$options_charts
|
||||
);
|
||||
|
||||
if ($pdf === true) {
|
||||
$return['chart']['by_agent'] .= '" />';
|
||||
} else {
|
||||
$return['chart']['by_agent'] .= '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($event_graph_by_user_validator) {
|
||||
$data_graph_by_user = events_get_count_events_validated_by_user($data);
|
||||
$return['chart']['by_user_validator'] = pie_graph(
|
||||
if ($pdf === true) {
|
||||
$return['chart']['by_user_validator'] = '<img src="data:image/png;base64,';
|
||||
} else {
|
||||
$return['chart']['by_user_validator'] = '<div style="margin: 0 auto; width:'.$options_charts['width'].'px;">';
|
||||
}
|
||||
|
||||
$return['chart']['by_user_validator'] .= pie_graph(
|
||||
$data_graph_by_user,
|
||||
500,
|
||||
150,
|
||||
__('other'),
|
||||
ui_get_full_url(false, false, false, false),
|
||||
ui_get_full_url(false, false, false, false).'/images/logo_vertical_water.png',
|
||||
$config['fontpath'],
|
||||
$config['font_size'],
|
||||
$ttl
|
||||
$options_charts
|
||||
);
|
||||
|
||||
if ($pdf === true) {
|
||||
$return['chart']['by_user_validator'] .= '" />';
|
||||
} else {
|
||||
$return['chart']['by_user_validator'] .= '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($event_graph_by_criticity) {
|
||||
|
@ -2213,20 +2242,26 @@ function reporting_event_report_group(
|
|||
}
|
||||
|
||||
$colors = get_criticity_pie_colors($data_graph_by_criticity);
|
||||
$options_charts['colors'] = array_values($colors);
|
||||
|
||||
$return['chart']['by_criticity'] = pie_graph(
|
||||
if ($pdf === true) {
|
||||
$return['chart']['by_criticity'] = '<img src="data:image/png;base64,';
|
||||
} else {
|
||||
$return['chart']['by_criticity'] = '<div style="margin: 0 auto; width:'.$options_charts['width'].'px;">';
|
||||
}
|
||||
|
||||
$return['chart']['by_criticity'] .= pie_graph(
|
||||
$data_graph_by_criticity,
|
||||
500,
|
||||
150,
|
||||
__('other'),
|
||||
ui_get_full_url(false, false, false, false),
|
||||
ui_get_full_url(false, false, false, false).'/images/logo_vertical_water.png',
|
||||
$config['fontpath'],
|
||||
$config['font_size'],
|
||||
$ttl,
|
||||
false,
|
||||
$colors
|
||||
$options_charts
|
||||
);
|
||||
|
||||
if ($pdf === true) {
|
||||
$return['chart']['by_criticity'] .= '" />';
|
||||
} else {
|
||||
$return['chart']['by_criticity'] .= '</div>';
|
||||
}
|
||||
|
||||
unset($options_charts['colors']);
|
||||
}
|
||||
|
||||
if ($event_graph_validated_vs_unvalidated) {
|
||||
|
@ -2246,17 +2281,22 @@ function reporting_event_report_group(
|
|||
}
|
||||
}
|
||||
|
||||
$return['chart']['validated_vs_unvalidated'] = pie_graph(
|
||||
if ($pdf === true) {
|
||||
$return['chart']['validated_vs_unvalidated'] = '<img src="data:image/png;base64,';
|
||||
} else {
|
||||
$return['chart']['validated_vs_unvalidated'] = '<div style="margin: 0 auto; width:'.$options_charts['width'].'px;">';
|
||||
}
|
||||
|
||||
$return['chart']['validated_vs_unvalidated'] .= pie_graph(
|
||||
$data_graph_by_status,
|
||||
500,
|
||||
150,
|
||||
__('other'),
|
||||
ui_get_full_url(false, false, false, false),
|
||||
ui_get_full_url(false, false, false, false).'/images/logo_vertical_water.png',
|
||||
$config['fontpath'],
|
||||
$config['font_size'],
|
||||
$ttl
|
||||
$options_charts
|
||||
);
|
||||
|
||||
if ($pdf === true) {
|
||||
$return['chart']['validated_vs_unvalidated'] .= '" />';
|
||||
} else {
|
||||
$return['chart']['validated_vs_unvalidated'] .= '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Total events.
|
||||
|
@ -15311,11 +15351,7 @@ function reporting_module_histogram_graph($report, $content, $pdf=0)
|
|||
$report['datetime']
|
||||
);
|
||||
} else {
|
||||
$return['chart'] = graph_nodata_image(
|
||||
$width_graph,
|
||||
$height_graph,
|
||||
'area'
|
||||
);
|
||||
$return['chart'] = graph_nodata_image(['height' => $height_graph]);
|
||||
}
|
||||
|
||||
if ($metaconsole_on && $server_name != '') {
|
||||
|
|
|
@ -5131,7 +5131,17 @@ function reporting_get_stats_summary($data, $graph_width, $graph_height)
|
|||
// Fixed width non interactive charts.
|
||||
$status_chart_width = $graph_width;
|
||||
|
||||
$tdata[0] = '<div style="margin: auto; width: '.$graph_width.'px;"><div id="status_pie" style="margin: auto; width: '.$graph_width.'">'.graph_agent_status(false, $graph_width, $graph_height, true, true).'</div></div>';
|
||||
$tdata[0] = '<div style="margin: auto; width: '.$graph_width.'px;">';
|
||||
$tdata[0] .= '<div id="status_pie" style="margin: auto; width: '.$graph_width.'">';
|
||||
$tdata[0] .= graph_agent_status(
|
||||
false,
|
||||
$graph_width,
|
||||
$graph_height,
|
||||
true,
|
||||
true
|
||||
);
|
||||
$tdata[0] .= '</div>';
|
||||
$tdata[0] .= '</div>';
|
||||
} else {
|
||||
$tdata[2] = html_print_image(
|
||||
'images/image_problem_area_small.png',
|
||||
|
@ -5141,7 +5151,16 @@ function reporting_get_stats_summary($data, $graph_width, $graph_height)
|
|||
}
|
||||
|
||||
if ($data['monitor_alerts'] > 0) {
|
||||
$tdata[2] = '<div style="margin: auto; width: '.$graph_width.'px;">'.graph_alert_status($data['monitor_alerts'], $data['monitor_alerts_fired'], $graph_width, $graph_height, true, true).'</div>';
|
||||
$tdata[2] = '<div style="margin: auto; width: '.$graph_width.'px;">';
|
||||
$tdata[2] .= graph_alert_status(
|
||||
$data['monitor_alerts'],
|
||||
$data['monitor_alerts_fired'],
|
||||
$graph_width,
|
||||
$graph_height,
|
||||
true,
|
||||
true
|
||||
);
|
||||
$tdata[2] .= '</div>';
|
||||
} else {
|
||||
$tdata[2] = html_print_image(
|
||||
'images/image_problem_area_small.png',
|
||||
|
@ -5150,8 +5169,8 @@ function reporting_get_stats_summary($data, $graph_width, $graph_height)
|
|||
);
|
||||
}
|
||||
|
||||
$table_sum->rowclass[] = '';
|
||||
$table_sum->data[] = $tdata;
|
||||
$table_sum->rowclass[] = '';
|
||||
$table_sum->data[] = $tdata;
|
||||
|
||||
$output = '<fieldset class="databox tactical_set">
|
||||
<legend>'.__('Summary').'</legend>'.html_print_table($table_sum, true).'</fieldset>';
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -11,6 +11,8 @@
|
|||
// Turn on output buffering.
|
||||
// The entire buffer will be discarded later so that any accidental output
|
||||
// does not corrupt images generated by fgraph.
|
||||
use Nutsy\PHPChartJS\Factory;
|
||||
|
||||
ob_start();
|
||||
|
||||
global $config;
|
||||
|
@ -566,14 +568,7 @@ function vbar_graph(
|
|||
}
|
||||
|
||||
if (empty($params['data']) === true) {
|
||||
return graph_nodata_image(
|
||||
0,
|
||||
0,
|
||||
'vbar',
|
||||
'',
|
||||
true,
|
||||
($ttl === 2) ? true : false
|
||||
);
|
||||
return graph_nodata_image($options);
|
||||
}
|
||||
|
||||
if ((int) $ttl === 2) {
|
||||
|
@ -730,7 +725,7 @@ function hbar_graph(
|
|||
}
|
||||
|
||||
if ($chart_data === false || empty($chart_data) === true) {
|
||||
return graph_nodata_image($width, $height, 'hbar');
|
||||
return graph_nodata_image($options);
|
||||
}
|
||||
|
||||
if ($ttl == 2) {
|
||||
|
@ -776,38 +771,57 @@ function hbar_graph(
|
|||
|
||||
function pie_graph(
|
||||
$chart_data,
|
||||
$width,
|
||||
$height,
|
||||
$others_str='other',
|
||||
$homedir='',
|
||||
$water_mark='',
|
||||
$font='',
|
||||
$font_size=8,
|
||||
$ttl=1,
|
||||
$legend_position=false,
|
||||
$colors='',
|
||||
$hide_labels=false,
|
||||
$max_values=9
|
||||
$options
|
||||
) {
|
||||
/*
|
||||
$width,
|
||||
$height,
|
||||
$others_str='other',
|
||||
$homedir='',
|
||||
$water_mark='',
|
||||
$font='',
|
||||
$font_size=8,
|
||||
$ttl=1,
|
||||
$legend_position=false,
|
||||
$colors='',
|
||||
$hide_labels=false,
|
||||
$max_values=9
|
||||
*/
|
||||
|
||||
if (empty($chart_data) === true) {
|
||||
return graph_nodata_image($width, $height, 'pie');
|
||||
return graph_nodata_image($options);
|
||||
}
|
||||
|
||||
if ($water_mark !== false) {
|
||||
if ((int) $options['ttl'] === 2) {
|
||||
$params = [
|
||||
'chart_data' => $chart_data,
|
||||
'options' => $options,
|
||||
'return_img_base_64' => true,
|
||||
];
|
||||
|
||||
return generator_chart_to_pdf('pie_graph', $params);
|
||||
}
|
||||
|
||||
$chart = get_build_setup_charts('PIE', $options, $chart_data);
|
||||
$output = $chart->render(true, true);
|
||||
return $output;
|
||||
|
||||
/*
|
||||
if ($water_mark !== false) {
|
||||
setup_watermark($water_mark, $water_mark_file, $water_mark_url);
|
||||
}
|
||||
}
|
||||
|
||||
// This library allows only 8 colors.
|
||||
// $max_values = 9;
|
||||
// Remove the html_entities.
|
||||
$temp = [];
|
||||
foreach ($chart_data as $key => $value) {
|
||||
// This library allows only 8 colors.
|
||||
// $max_values = 9;
|
||||
// Remove the html_entities.
|
||||
$temp = [];
|
||||
foreach ($chart_data as $key => $value) {
|
||||
$temp[io_safe_output($key)] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$chart_data = $temp;
|
||||
$chart_data = $temp;
|
||||
|
||||
if (count($chart_data) > $max_values) {
|
||||
if (count($chart_data) > $max_values) {
|
||||
$chart_data_trunc = [];
|
||||
$n = 1;
|
||||
foreach ($chart_data as $key => $value) {
|
||||
|
@ -825,9 +839,9 @@ function pie_graph(
|
|||
}
|
||||
|
||||
$chart_data = $chart_data_trunc;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ttl == 2) {
|
||||
if ($ttl == 2) {
|
||||
$params = [
|
||||
'values' => array_values($chart_data),
|
||||
'keys' => array_keys($chart_data),
|
||||
|
@ -842,9 +856,9 @@ function pie_graph(
|
|||
];
|
||||
|
||||
return generator_chart_to_pdf('pie_chart', $params);
|
||||
}
|
||||
}
|
||||
|
||||
return flot_pie_chart(
|
||||
return flot_pie_chart(
|
||||
array_values($chart_data),
|
||||
array_keys($chart_data),
|
||||
$width,
|
||||
|
@ -855,77 +869,293 @@ function pie_graph(
|
|||
$legend_position,
|
||||
$colors,
|
||||
$hide_labels
|
||||
);
|
||||
);
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rin graph DOUGHNUT.
|
||||
*
|
||||
* @param array $chart_data Data.
|
||||
* @param array $options Options.
|
||||
*
|
||||
* @return string Output html charts
|
||||
*/
|
||||
function ring_graph(
|
||||
$chart_data,
|
||||
$width,
|
||||
$height,
|
||||
$others_str='other',
|
||||
$homedir='',
|
||||
$water_mark='',
|
||||
$font='',
|
||||
$font_size='',
|
||||
$ttl=1,
|
||||
$legend_position=false,
|
||||
$colors='',
|
||||
$hide_labels=false,
|
||||
$background_color='white',
|
||||
$pdf=false
|
||||
$options
|
||||
) {
|
||||
if (empty($chart_data)) {
|
||||
return graph_nodata_image($width, $height, 'pie');
|
||||
global $config;
|
||||
// TODO: XXX chartjs.
|
||||
// $ttl
|
||||
// $hide_labels
|
||||
// $background_color
|
||||
// $pdf
|
||||
if (empty($chart_data) === true) {
|
||||
return graph_nodata_image($options);
|
||||
}
|
||||
|
||||
setup_watermark($water_mark, $water_mark_file, $water_mark_url);
|
||||
|
||||
// This library allows only 8 colors
|
||||
$max_values = 18;
|
||||
|
||||
if ($ttl == 2) {
|
||||
if ((int) $options['ttl'] === 2) {
|
||||
$params = [
|
||||
'chart_data' => $chart_data,
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'colors' => $colors,
|
||||
'module_name_list' => $module_name_list,
|
||||
'long_index' => $long_index,
|
||||
'no_data' => $no_data,
|
||||
'water_mark' => $water_mark,
|
||||
'font' => $font,
|
||||
'font_size' => $font_size,
|
||||
'unit' => $unit,
|
||||
'ttl' => $ttl,
|
||||
'homeurl' => $homeurl,
|
||||
'background_color' => $background_color,
|
||||
'legend_position' => $legend_position,
|
||||
'pdf' => $pdf,
|
||||
'chart_data' => $chart_data,
|
||||
'options' => $options,
|
||||
'return_img_base_64' => true,
|
||||
];
|
||||
|
||||
return generator_chart_to_pdf('ring_graph', $params);
|
||||
}
|
||||
|
||||
return flot_custom_pie_chart(
|
||||
$chart_data,
|
||||
$width,
|
||||
$height,
|
||||
$colors,
|
||||
$module_name_list,
|
||||
$long_index,
|
||||
$no_data,
|
||||
false,
|
||||
'',
|
||||
$water_mark,
|
||||
$font,
|
||||
$font_size,
|
||||
$unit,
|
||||
$ttl,
|
||||
$homeurl,
|
||||
$background_color,
|
||||
$legend_position,
|
||||
$background_color,
|
||||
$pdf
|
||||
);
|
||||
$chart = get_build_setup_charts('DOUGHNUT', $options, $chart_data);
|
||||
$output = $chart->render(true, true);
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
function get_build_setup_charts($type, $options, $data)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$factory = new Factory();
|
||||
|
||||
switch ($type) {
|
||||
case 'DOUGHNUT':
|
||||
$chart = $factory->create($factory::DOUGHNUT);
|
||||
break;
|
||||
|
||||
case 'PIE':
|
||||
$chart = $factory->create($factory::PIE);
|
||||
break;
|
||||
|
||||
default:
|
||||
// code...
|
||||
break;
|
||||
}
|
||||
|
||||
$example = [
|
||||
'id' => null,
|
||||
'width' => null,
|
||||
'height' => null,
|
||||
'maintainAspectRatio' => false,
|
||||
'responsive' => true,
|
||||
'radius' => null,
|
||||
'rotation' => null,
|
||||
'circumference' => null,
|
||||
'legend' => [
|
||||
'display' => true,
|
||||
'position' => 'top',
|
||||
'align' => 'center',
|
||||
'font' => [
|
||||
'family' => '',
|
||||
'size' => 12,
|
||||
'style' => 'normal',
|
||||
'weight' => null,
|
||||
'lineHeight' => 1.2,
|
||||
],
|
||||
],
|
||||
'title' => [
|
||||
'display' => true,
|
||||
'position' => 'top',
|
||||
'color' => '',
|
||||
'align' => 'center',
|
||||
'text' => '',
|
||||
'font' => [
|
||||
'family' => '',
|
||||
'size' => 12,
|
||||
'style' => 'normal',
|
||||
'weight' => null,
|
||||
'lineHeight' => 1.2,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Set Id.
|
||||
$id = uniqid('graph_');
|
||||
if (isset($options['id']) === true && empty($options['id']) === false) {
|
||||
$id = $options['id'];
|
||||
}
|
||||
|
||||
$chart->setId($id);
|
||||
|
||||
// Height is null maximum possible.
|
||||
if (isset($options['height']) === true
|
||||
&& empty($options['height']) === false
|
||||
) {
|
||||
$chart->setHeight($options['height']);
|
||||
}
|
||||
|
||||
// Width is null maximum possible.
|
||||
if (isset($options['width']) === true
|
||||
&& empty($options['width']) === false
|
||||
) {
|
||||
$chart->setWidth($options['width']);
|
||||
}
|
||||
|
||||
// Fonts.
|
||||
// $chart->defaults()->getFonts()->setFamily($config['fontpath']);
|
||||
// $chart->defaults()->getFonts()->setStyle('normal');
|
||||
$chart->defaults()->getFonts()->setSize(($config['font_size'] + 5));
|
||||
|
||||
if (isset($options['waterMark']) === true
|
||||
&& empty($options['waterMark']) === false
|
||||
) {
|
||||
// WaterMark.
|
||||
$chart->defaults()->getWaterMark()->setWidth(88);
|
||||
$chart->defaults()->getWaterMark()->setHeight(16);
|
||||
$chart->defaults()->getWaterMark()->setSrc($options['waterMark']['url']);
|
||||
$chart->defaults()->getWaterMark()->setPosition('end');
|
||||
$chart->defaults()->getWaterMark()->setAlign('top');
|
||||
}
|
||||
|
||||
if (isset($options['pdf']) === true && $options['pdf'] === true) {
|
||||
$chart->options()->disableAnimation(false);
|
||||
}
|
||||
|
||||
// Set Maintain Aspect Ratio for responsive charts.
|
||||
$maintainAspectRatio = false;
|
||||
if (isset($options['maintainAspectRatio']) === true
|
||||
&& empty($options['maintainAspectRatio']) === false
|
||||
) {
|
||||
$maintainAspectRatio = $options['maintainAspectRatio'];
|
||||
}
|
||||
|
||||
$chart->options()->setMaintainAspectRatio($maintainAspectRatio);
|
||||
|
||||
// Set Responsive for responsive charts.
|
||||
$responsive = true;
|
||||
if (isset($options['responsive']) === true
|
||||
&& empty($options['responsive']) === false
|
||||
) {
|
||||
$responsive = $options['responsive'];
|
||||
}
|
||||
|
||||
$chart->options()->setResponsive($responsive);
|
||||
|
||||
// LEGEND.
|
||||
// Set Display legends.
|
||||
$legendDisplay = true;
|
||||
if (isset($options['legend']['display']) === true) {
|
||||
$legendDisplay = $options['legend']['display'];
|
||||
}
|
||||
|
||||
$chart->options()->getPlugins()->getLegend()->setDisplay($legendDisplay);
|
||||
|
||||
// Set Position legends.
|
||||
$legendPosition = 'top';
|
||||
if (isset($options['legend']['position']) === true
|
||||
&& empty($options['legend']['position']) === false
|
||||
) {
|
||||
$legendPosition = $options['legend']['position'];
|
||||
}
|
||||
|
||||
$chart->options()->getPlugins()->getLegend()->setPosition($legendPosition);
|
||||
|
||||
// Set Align legends.
|
||||
$legendAlign = 'center';
|
||||
if (isset($options['legend']['align']) === true
|
||||
&& empty($options['legend']['align']) === false
|
||||
) {
|
||||
$legendAlign = $options['legend']['align'];
|
||||
}
|
||||
|
||||
$chart->options()->getPlugins()->getLegend()->setAlign($legendAlign);
|
||||
|
||||
// Title.
|
||||
if (isset($options['title']) === true
|
||||
&& empty($options['title']) === false
|
||||
&& is_array($options['title']) === true
|
||||
) {
|
||||
$display = false;
|
||||
if (isset($options['title']['display']) === true) {
|
||||
$display = $options['title']['display'];
|
||||
}
|
||||
|
||||
$chart->options()->getPlugins()->getTitle()->setDisplay($display);
|
||||
|
||||
$text = __('Title');
|
||||
if (isset($options['title']['text']) === true) {
|
||||
$text = $options['title']['text'];
|
||||
}
|
||||
|
||||
$chart->options()->getPlugins()->getTitle()->setText($text);
|
||||
|
||||
$position = 'top';
|
||||
if (isset($options['title']['position']) === true) {
|
||||
$position = $options['title']['position'];
|
||||
}
|
||||
|
||||
$chart->options()->getPlugins()->getTitle()->setPosition($position);
|
||||
|
||||
$color = 'top';
|
||||
if (isset($options['title']['color']) === true) {
|
||||
$color = $options['title']['color'];
|
||||
}
|
||||
|
||||
$chart->options()->getPlugins()->getTitle()->setColor($color);
|
||||
|
||||
if (isset($options['title']['fonts']) === true
|
||||
&& empty($options['title']['fonts']) === false
|
||||
&& is_array($options['title']['fonts']) === true
|
||||
) {
|
||||
if (isset($options['title']['fonts']['size']) === true) {
|
||||
$chart->options()->getPlugins()->getTitle()->getFonts()->setSize($options['title']['fonts']['size']);
|
||||
}
|
||||
|
||||
if (isset($options['title']['fonts']['style']) === true) {
|
||||
$chart->options()->getPlugins()->getTitle()->getFonts()->setStyle($options['title']['fonts']['style']);
|
||||
}
|
||||
|
||||
if (isset($options['title']['fonts']['family']) === true) {
|
||||
$chart->options()->getPlugins()->getTitle()->getFonts()->setFamily($options['title']['fonts']['family']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Radius is null maximum possible.
|
||||
if (isset($options['radius']) === true
|
||||
&& empty($options['radius']) === false
|
||||
) {
|
||||
$chart->setRadius($options['radius']);
|
||||
}
|
||||
|
||||
// Rotation is null 0º.
|
||||
if (isset($options['rotation']) === true
|
||||
&& empty($options['rotation']) === false
|
||||
) {
|
||||
$chart->setRotation($options['rotation']);
|
||||
}
|
||||
|
||||
// Circumferende is null 360º.
|
||||
if (isset($options['circumference']) === true
|
||||
&& empty($options['circumference']) === false
|
||||
) {
|
||||
$chart->setCircumference($options['circumference']);
|
||||
}
|
||||
|
||||
// Color.
|
||||
if (isset($options['colors']) === true
|
||||
&& empty($options['colors']) === false
|
||||
&& is_array($options['colors']) === true
|
||||
) {
|
||||
$colors = $options['colors'];
|
||||
} else {
|
||||
// Colors.
|
||||
$defaultColor = [];
|
||||
$defaultColorArray = color_graph_array();
|
||||
foreach ($defaultColorArray as $key => $value) {
|
||||
$defaultColor[$key] = $value['color'];
|
||||
}
|
||||
|
||||
$colors = array_values($defaultColor);
|
||||
}
|
||||
|
||||
// Set labels.
|
||||
$chart->labels()->exchangeArray(array_keys($data));
|
||||
|
||||
// Add Datasets.
|
||||
$setData = $chart->createDataSet();
|
||||
$setData->setLabel('data')->setBackgroundColor($colors)->data()->exchangeArray(array_values($data));
|
||||
$chart->addDataSet($setData);
|
||||
|
||||
return $chart;
|
||||
}
|
||||
|
|
|
@ -756,71 +756,3 @@ function print_clock_digital_1($time_format, $timezone, $clock_animation, $width
|
|||
return $output;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print dougnhnut.
|
||||
*
|
||||
* @param array $colors Colors.
|
||||
* @param integer $width Width.
|
||||
* @param integer $height Height.
|
||||
* @param array $data Data.
|
||||
* @param mixed $data_total Data_total.
|
||||
*
|
||||
* @return string HTML.
|
||||
*/
|
||||
function print_donut_narrow_graph(
|
||||
array $colors,
|
||||
$width,
|
||||
$height,
|
||||
array $data,
|
||||
$data_total
|
||||
) {
|
||||
global $config;
|
||||
|
||||
if (empty($data)) {
|
||||
return graph_nodata_image($width, $height, 'pie');
|
||||
}
|
||||
|
||||
$series = count($data);
|
||||
if (($series != count($colors)) || ($series == 0)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$data = json_encode($data);
|
||||
$colors = json_encode($colors);
|
||||
|
||||
$graph_id = uniqid('graph_');
|
||||
|
||||
// This is for "Style template" in visual styles.
|
||||
switch ($config['style']) {
|
||||
case 'pandora':
|
||||
$textColor = '#000';
|
||||
$strokeColor = '#fff';
|
||||
break;
|
||||
|
||||
case 'pandora_black':
|
||||
$textColor = '#fff';
|
||||
$strokeColor = '#222';
|
||||
break;
|
||||
|
||||
default:
|
||||
$textColor = '#000';
|
||||
$strokeColor = '#fff';
|
||||
break;
|
||||
}
|
||||
|
||||
$textColor = json_encode($textColor);
|
||||
$strokeColor = json_encode($strokeColor);
|
||||
|
||||
$out = "<div id='$graph_id'></div>";
|
||||
$out .= include_javascript_d3(true);
|
||||
$out .= "<script type='text/javascript'>
|
||||
donutNarrowGraph($colors, $width, $height, $data_total, $textColor, $strokeColor)
|
||||
.donutbody(d3.select($graph_id))
|
||||
.data($data)
|
||||
.render();
|
||||
</script>";
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,10 @@ function include_javascript_dependencies_flot_graph($return=false, $mobile=false
|
|||
<script language="javascript" type="text/javascript" src="'.ui_get_full_url($metaconsole_hack.'/include/graphs/flot/jquery.flot.exportdata.pandora.js').'"></script>
|
||||
<script language="javascript" type="text/javascript" src="'.ui_get_full_url($metaconsole_hack.'/include/graphs/flot/jquery.flot.axislabels.js').'"></script>
|
||||
<script language="javascript" type="text/javascript" src="'.ui_get_full_url($metaconsole_hack.'/include/graphs/flot/pandora.flot.js').'"></script>';
|
||||
|
||||
// Chartjs.
|
||||
$output .= '<script language="javascript" type="text/javascript" src="'.ui_get_full_url($metaconsole_hack.'/include/graphs/chartjs/chart.js').'"></script>';
|
||||
|
||||
$output .= "
|
||||
<script type='text/javascript'>
|
||||
var precision_graph = ".$config['graph_precision'].";
|
||||
|
|
|
@ -2797,229 +2797,3 @@ function valueToBytes(value) {
|
|||
// This will actually do the rounding and the decimals.
|
||||
return value.toFixed(2) + shorts[pos] + "B";
|
||||
}
|
||||
|
||||
function donutNarrowGraph(
|
||||
colores,
|
||||
width,
|
||||
height,
|
||||
total,
|
||||
textColor,
|
||||
strokeColor
|
||||
) {
|
||||
// Default settings
|
||||
var donutbody = d3.select("body");
|
||||
var data = {};
|
||||
// var showTitle = true;
|
||||
|
||||
if (width == "") {
|
||||
width = 180;
|
||||
}
|
||||
|
||||
if (height == "") {
|
||||
height = 180;
|
||||
}
|
||||
|
||||
var radius = Math.min(width, height) / 2;
|
||||
|
||||
var currentVal;
|
||||
//var color = d3.scale.category20();
|
||||
|
||||
var colores_index = [];
|
||||
var colores_value = [];
|
||||
|
||||
$.each(colores, function(index, value) {
|
||||
colores_index.push(index);
|
||||
colores_value.push(value);
|
||||
});
|
||||
|
||||
var color = d3.scale
|
||||
.ordinal()
|
||||
.domain(colores_index)
|
||||
.range(colores_value);
|
||||
|
||||
var pie = d3.layout
|
||||
.pie()
|
||||
.sort(null)
|
||||
.value(function(d) {
|
||||
return d.value;
|
||||
});
|
||||
|
||||
var svg, g, arc;
|
||||
|
||||
var object = {};
|
||||
|
||||
// Method for render/refresh graph
|
||||
object.render = function() {
|
||||
if (!svg) {
|
||||
// Show normal status by default. This variable must be initialized here, before clearing data.
|
||||
var normal_status = data.Normal;
|
||||
|
||||
// Don't draw 0 or invalid values. console.log(data);
|
||||
var data_map = $.map(data, function(value, index) {
|
||||
if (value == 0 || isNaN(value)) {
|
||||
return index;
|
||||
}
|
||||
});
|
||||
|
||||
$.each(data_map, function(i, val) {
|
||||
delete data[val];
|
||||
});
|
||||
//New data: console.log(data);
|
||||
|
||||
arc = d3.svg
|
||||
.arc()
|
||||
.outerRadius(radius)
|
||||
.innerRadius(radius - radius / 4);
|
||||
|
||||
svg = donutbody
|
||||
.append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
|
||||
|
||||
g = svg
|
||||
.selectAll(".arc")
|
||||
.data(pie(d3.entries(data)))
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("class", "arc");
|
||||
|
||||
g.append("path")
|
||||
// Attach current value to g so that we can use it for animation
|
||||
.each(function(d) {
|
||||
this._current = d;
|
||||
})
|
||||
.attr("d", arc)
|
||||
.attr("stroke", strokeColor)
|
||||
.style("fill", function(d) {
|
||||
return color(d.data.key);
|
||||
});
|
||||
// This is to show labels on the graph
|
||||
/* g.append("text")
|
||||
.attr("transform", function(d) {
|
||||
return "translate(" + arc.centroid(d) + ")";
|
||||
})
|
||||
.attr("dy", ".35em")
|
||||
.style("text-anchor", "middle");
|
||||
g.select("text").text(function(d) {
|
||||
return d.data.key;
|
||||
});*/
|
||||
|
||||
// Show normal status by default.
|
||||
var percentage_normal;
|
||||
svg
|
||||
.append("text")
|
||||
.datum(data)
|
||||
.attr("x", 0)
|
||||
.attr("y", 0 + radius / 10)
|
||||
.attr("class", "text-tooltip")
|
||||
.style("text-anchor", "middle")
|
||||
.attr("fill", textColor)
|
||||
.style("font-size", function(d) {
|
||||
if (normal_status) {
|
||||
percentage_normal = (normal_status * 100) / total;
|
||||
if (Number.isInteger(percentage_normal)) {
|
||||
percentage_normal = percentage_normal.toFixed(0);
|
||||
return radius / 3 + "px";
|
||||
} else {
|
||||
percentage_normal = percentage_normal.toFixed(1);
|
||||
return radius / 3.5 + "px";
|
||||
}
|
||||
}
|
||||
})
|
||||
.text(function(d) {
|
||||
if (normal_status) {
|
||||
return percentage_normal + "%";
|
||||
} else {
|
||||
return "0%";
|
||||
}
|
||||
});
|
||||
|
||||
g.on("mouseover", function(obj) {
|
||||
//console.log(obj);
|
||||
var percentage;
|
||||
svg
|
||||
.select("text.text-tooltip")
|
||||
// This is to paint the text of the corresponding color.
|
||||
/* .attr("fill", function(d) {
|
||||
return color(obj.data.key);
|
||||
})*/
|
||||
.attr("fill", textColor)
|
||||
.style("font-size", function(d) {
|
||||
percentage = (d[obj.data.key] * 100) / total;
|
||||
if (Number.isInteger(percentage)) {
|
||||
percentage = percentage.toFixed(0);
|
||||
return radius / 3 + "px";
|
||||
} else {
|
||||
percentage = percentage.toFixed(1);
|
||||
return radius / 3.5 + "px";
|
||||
}
|
||||
})
|
||||
.text(percentage + "%");
|
||||
});
|
||||
|
||||
g.on("mouseout", function(obj) {
|
||||
svg.select("text.text-tooltip").text(function(d) {
|
||||
if (normal_status) {
|
||||
return percentage_normal + "%";
|
||||
} else {
|
||||
return "0%";
|
||||
}
|
||||
});
|
||||
// .attr("fill", "#82b92e");
|
||||
});
|
||||
} else {
|
||||
g.data(pie(d3.entries(data)))
|
||||
.exit()
|
||||
.remove();
|
||||
|
||||
g.select("path")
|
||||
.transition()
|
||||
.duration(200)
|
||||
.attrTween("d", function(a) {
|
||||
var i = d3.interpolate(this._current, a);
|
||||
this._current = i(0);
|
||||
return function(t) {
|
||||
return arc(i(t));
|
||||
};
|
||||
});
|
||||
|
||||
g.select("text").attr("transform", function(d) {
|
||||
return "translate(" + arc.centroid(d) + ")";
|
||||
});
|
||||
|
||||
svg.select("text.text-tooltip").datum(data);
|
||||
}
|
||||
return object;
|
||||
};
|
||||
|
||||
// Getter and setter methods
|
||||
object.data = function(value) {
|
||||
if (!arguments.length) return data;
|
||||
data = value;
|
||||
return object;
|
||||
};
|
||||
|
||||
object.donutbody = function(value) {
|
||||
if (!arguments.length) return donutbody;
|
||||
donutbody = value;
|
||||
return object;
|
||||
};
|
||||
|
||||
object.width = function(value) {
|
||||
if (!arguments.length) return width;
|
||||
width = value;
|
||||
radius = Math.min(width, height) / 2;
|
||||
return object;
|
||||
};
|
||||
|
||||
object.height = function(value) {
|
||||
if (!arguments.length) return height;
|
||||
height = value;
|
||||
radius = Math.min(width, height) / 2;
|
||||
return object;
|
||||
};
|
||||
|
||||
return object;
|
||||
}
|
||||
|
|
|
@ -222,6 +222,7 @@ if (check_acl($config['id_user'], 0, 'ER')) {
|
|||
$event_filter .= ' AND utimestamp > (UNIX_TIMESTAMP(NOW()) - '.($config['event_view_hr'] * SECONDS_1HOUR).')';
|
||||
}
|
||||
|
||||
hd('aaaaaaaaaaaaaaa');
|
||||
$events = events_print_event_table($event_filter, 10, '100%', true, false, true);
|
||||
ui_toggle(
|
||||
$events,
|
||||
|
@ -240,13 +241,15 @@ if ($is_admin) {
|
|||
include $config['homedir'].'/godmode/servers/servers.build_table.php';
|
||||
}
|
||||
|
||||
$out = '<table cellpadding=0 cellspacing=0 class="databox pies mrgn_top_15px" width=100%><tr><td>';
|
||||
$out .= '<fieldset class="padding-0 databox tactical_set" id="total_event_graph">
|
||||
<legend>'.__('Event graph').'</legend>'.html_print_image('images/spinner.gif', true, ['id' => 'spinner_total_event_graph']).'</fieldset>';
|
||||
$out .= '</td><td>';
|
||||
$out .= '<fieldset class="padding-0 databox tactical_set" id="graphic_event_group">
|
||||
<legend>'.__('Event graph by agent').'</legend>'.html_print_image('images/spinner.gif', true, ['id' => 'spinner_graphic_event_group']).'</fieldset>';
|
||||
$out .= '</td></tr></table>';
|
||||
$out = '<table cellpadding=0 cellspacing=0 class="databox pies mrgn_top_15px" width=100%><tr><td style="width:50%;">';
|
||||
$out .= '<fieldset class="padding-0 databox tactical_set" id="total_event_graph">';
|
||||
$out .= '<legend>'.__('Event graph').'</legend>';
|
||||
$out .= html_print_image('images/spinner.gif', true, ['id' => 'spinner_total_event_graph']);
|
||||
$out .= '</fieldset>';
|
||||
$out .= '</td><td style="width:50%;">';
|
||||
$out .= '<fieldset class="padding-0 databox tactical_set" id="graphic_event_group">
|
||||
<legend>'.__('Event graph by agent').'</legend>'.html_print_image('images/spinner.gif', true, ['id' => 'spinner_graphic_event_group']).'</fieldset>';
|
||||
$out .= '</td></tr></table>';
|
||||
|
||||
|
||||
ui_toggle(
|
||||
|
|
|
@ -307,34 +307,6 @@ if ($view_graph) {
|
|||
return;
|
||||
}
|
||||
|
||||
if ($graph_return) {
|
||||
echo "<table id='graph-container' class='databox filters' cellpadding='0' cellspacing='0' width='100%'>";
|
||||
echo '<tr><td>';
|
||||
if (!is_ajax()) {
|
||||
echo '<div id="spinner_loading" class="loading invisible" style="display:flex;flex-direction:column-reverse;justify-content:center;align-items:center">';
|
||||
echo html_print_image('images/spinner.gif', true, ['width' => '20px']);
|
||||
echo __('Loading').'…';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
if ($stacked == CUSTOM_GRAPH_VBARS) {
|
||||
echo '<div class="w100p height_600px">';
|
||||
echo '<div id="div-container" class="w100p height_600px">';
|
||||
} else {
|
||||
echo '<div id="div-container">';
|
||||
}
|
||||
|
||||
echo $graph_return;
|
||||
echo '</div>';
|
||||
if ($stacked == CUSTOM_GRAPH_VBARS) {
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '</td></tr></table>';
|
||||
} else {
|
||||
ui_print_info_message([ 'no_close' => true, 'message' => __('No data.') ]);
|
||||
}
|
||||
|
||||
if ($stacked == CUSTOM_GRAPH_BULLET_CHART_THRESHOLD) {
|
||||
$stacked = 4;
|
||||
}
|
||||
|
@ -403,6 +375,31 @@ if ($view_graph) {
|
|||
echo '</table>';
|
||||
echo '</form>';
|
||||
|
||||
if ($graph_return) {
|
||||
echo "<table id='graph-container' class='databox filters' cellpadding='0' cellspacing='0' style='height:100%;width:100%;overflow:hidden;'>";
|
||||
echo '<tr><td>';
|
||||
if (!is_ajax()) {
|
||||
echo '<div id="spinner_loading" class="loading invisible" style="display:flex;flex-direction:column-reverse;justify-content:center;align-items:center">';
|
||||
echo html_print_image('images/spinner.gif', true, ['width' => '20px']);
|
||||
echo __('Loading').'…';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '<div id="div-container" class="w100p" style="height:100%;">';
|
||||
|
||||
echo '<div style="position: relative; display: flex; flex-direction:row; justify-content: center; align-items: center; align-content: center; width:100%; height:50vh;">';
|
||||
echo '<div style="flex: 0 0 auto; width:99%; height:100%;">';
|
||||
echo $graph_return;
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
|
||||
echo '</td></tr></table>';
|
||||
} else {
|
||||
ui_print_info_message([ 'no_close' => true, 'message' => __('No data.') ]);
|
||||
}
|
||||
|
||||
/*
|
||||
We must add javascript here. Otherwise, the date picker won't
|
||||
work if the date is not correct because php is returning.
|
||||
|
|
|
@ -209,7 +209,7 @@ $table_source_row['table'] = html_print_table($table_source_data, true);
|
|||
unset($table_source_data);
|
||||
|
||||
if (empty($table_source_graph_data)) {
|
||||
$table_source_graph = graph_nodata_image();
|
||||
$table_source_graph = graph_nodata_image([]);
|
||||
} else {
|
||||
$table_source_graph = pie_graph(
|
||||
$table_source_graph_data,
|
||||
|
@ -266,7 +266,7 @@ $table_oid_row['table'] = html_print_table($table_oid_data, true);
|
|||
unset($table_oid_data);
|
||||
|
||||
if (empty($table_oid_graph_data)) {
|
||||
$table_oid_graph = graph_nodata_image();
|
||||
$table_oid_graph = graph_nodata_image([]);
|
||||
} else {
|
||||
$table_oid_graph = pie_graph(
|
||||
$table_oid_graph_data,
|
||||
|
|
|
@ -3,8 +3,21 @@
|
|||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
exit(1);
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2020 Soufiane Ghzal <sghzal@gmail.com>
|
||||
Copyright (c) 2020-2022 Graham Campbell <hello@gjcampbell.co.uk>
|
||||
Copyright (c) 2020-2022 Enrico Dias <enricodias@gmail.com>
|
||||
|
||||
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.
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"name": "chrome-php/chrome",
|
||||
"description": "Instrument headless chrome/chromium instances from PHP",
|
||||
"keywords": ["chrome", "chromium", "crawl", "browser", "headless", "screenshot", "pdf", "puppeteer"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Enrico Dias",
|
||||
"email": "enrico@enricodias.com",
|
||||
"homepage": "https://github.com/enricodias"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.3 || ^8.0",
|
||||
"chrome-php/wrench": "^1.3",
|
||||
"evenement/evenement": "^3.0.1",
|
||||
"monolog/monolog": "^1.27.1 || ^2.8 || ^3.2",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0",
|
||||
"symfony/filesystem": "^4.4 || ^5.0 || ^6.0",
|
||||
"symfony/polyfill-mbstring": "^1.26",
|
||||
"symfony/process": "^4.4 || ^5.0 || ^6.0"
|
||||
},
|
||||
"require-dev":{
|
||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
||||
"phpunit/phpunit": "^9.5.23",
|
||||
"symfony/var-dumper": "^4.4 || ^5.0 || ^6.0"
|
||||
},
|
||||
"autoload":{
|
||||
"psr-4" : {
|
||||
"HeadlessChromium\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev":{
|
||||
"psr-4" : {
|
||||
"HeadlessChromium\\Test\\": "tests/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"bamarni/composer-bin-plugin": true
|
||||
},
|
||||
"preferred-install": "dist"
|
||||
},
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium;
|
||||
|
||||
class AutoDiscover
|
||||
{
|
||||
/**
|
||||
* @var callable(): string
|
||||
*/
|
||||
private $osFamily;
|
||||
|
||||
/**
|
||||
* @param (callable(): string)|null $osFamily
|
||||
*/
|
||||
public function __construct(?callable $osFamily = null)
|
||||
{
|
||||
$this->osFamily = $osFamily ?? function (): string {
|
||||
return \PHP_OS_FAMILY;
|
||||
};
|
||||
}
|
||||
|
||||
public function guessChromeBinaryPath(): string
|
||||
{
|
||||
if (\array_key_exists('CHROME_PATH', $_SERVER)) {
|
||||
return $_SERVER['CHROME_PATH'];
|
||||
}
|
||||
|
||||
switch (($this->osFamily)()) {
|
||||
case 'Darwin':
|
||||
return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
||||
case 'Windows':
|
||||
return self::getFromRegistry() ?? '%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe';
|
||||
default:
|
||||
return null === self::shellExec('command -v google-chrome') ? 'chrome' : 'google-chrome';
|
||||
}
|
||||
}
|
||||
|
||||
private static function getFromRegistry(): ?string
|
||||
{
|
||||
$registryKey = self::shellExec(
|
||||
'reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe" /ve'
|
||||
);
|
||||
|
||||
if (null === $registryKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
\preg_match('/.:(?!.*:).*/', $registryKey, $matches);
|
||||
|
||||
return $matches[0] ?? null;
|
||||
}
|
||||
|
||||
private static function shellExec(string $command): ?string
|
||||
{
|
||||
try {
|
||||
$result = @\shell_exec($command);
|
||||
|
||||
return \is_string($result) ? $result : null;
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium;
|
||||
|
||||
use HeadlessChromium\Communication\Connection;
|
||||
use HeadlessChromium\Communication\Message;
|
||||
use HeadlessChromium\Communication\Target;
|
||||
use HeadlessChromium\Exception\CommunicationException;
|
||||
use HeadlessChromium\Exception\CommunicationException\ResponseHasError;
|
||||
use HeadlessChromium\Exception\NoResponseAvailable;
|
||||
use HeadlessChromium\Exception\OperationTimedOut;
|
||||
|
||||
class Browser
|
||||
{
|
||||
/**
|
||||
* @var Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var array<string,Target>
|
||||
*/
|
||||
protected $targets = [];
|
||||
|
||||
/**
|
||||
* @var array<string,Page>
|
||||
*/
|
||||
protected $pages = [];
|
||||
|
||||
/**
|
||||
* A preScript to be automatically added on every new pages.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $pagePreScript;
|
||||
|
||||
public function __construct(Connection $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
|
||||
// listen for target created
|
||||
$this->connection->on(Connection::EVENT_TARGET_CREATED, function (array $params): void {
|
||||
// create and store the target
|
||||
$this->targets[$params['targetInfo']['targetId']] = new Target($params['targetInfo'], $this->connection);
|
||||
});
|
||||
|
||||
// listen for target info changed
|
||||
$this->connection->on(Connection::EVENT_TARGET_INFO_CHANGED, function (array $params): void {
|
||||
// get target by id
|
||||
$target = $this->getTarget($params['targetInfo']['targetId']);
|
||||
|
||||
if ($target) {
|
||||
$target->targetInfoChanged($params['targetInfo']);
|
||||
}
|
||||
});
|
||||
|
||||
// listen for target destroyed
|
||||
$this->connection->on(Connection::EVENT_TARGET_DESTROYED, function (array $params): void {
|
||||
// get target by id
|
||||
$target = $this->getTarget($params['targetId']);
|
||||
|
||||
if ($target) {
|
||||
// remove the page
|
||||
unset($this->pages[$params['targetId']]);
|
||||
// remove the target
|
||||
unset($this->targets[$params['targetId']]);
|
||||
$target->destroy();
|
||||
$this->connection
|
||||
->getLogger()
|
||||
->debug('✘ target('.$params['targetId'].') was destroyed and unreferenced.');
|
||||
}
|
||||
});
|
||||
|
||||
// enable target discovery
|
||||
$connection->sendMessageSync(new Message('Target.setDiscoverTargets', ['discover' => true]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Connection
|
||||
*/
|
||||
public function getConnection(): Connection
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a preScript to be added on every new pages.
|
||||
* Use null to disable it.
|
||||
*
|
||||
* @param string|null $script
|
||||
*/
|
||||
public function setPagePreScript(string $script = null): void
|
||||
{
|
||||
$this->pagePreScript = $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the browser.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->sendCloseMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send close message to the browser.
|
||||
*
|
||||
* @throws OperationTimedOut
|
||||
*/
|
||||
final public function sendCloseMessage(): void
|
||||
{
|
||||
$r = $this->connection->sendMessageSync(new Message('Browser.close'));
|
||||
if (!$r->isSuccessful()) {
|
||||
// log
|
||||
$this->connection->getLogger()->debug('process: ✗ could not close gracefully');
|
||||
throw new \Exception('cannot close, Browser.close not supported');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new page.
|
||||
*
|
||||
* @throws NoResponseAvailable
|
||||
* @throws CommunicationException
|
||||
* @throws OperationTimedOut
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function createPage(): Page
|
||||
{
|
||||
// page url
|
||||
$params = ['url' => 'about:blank'];
|
||||
|
||||
// create page and get target id
|
||||
$response = $this->connection->sendMessageSync(new Message('Target.createTarget', $params));
|
||||
$targetId = $response['result']['targetId'];
|
||||
|
||||
// todo handle error
|
||||
|
||||
$target = $this->getTarget($targetId);
|
||||
if (!$target) {
|
||||
throw new \RuntimeException('Target could not be created for page.');
|
||||
}
|
||||
|
||||
$page = $this->getPage($targetId);
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $targetId
|
||||
*
|
||||
* @return Target|null
|
||||
*/
|
||||
public function getTarget($targetId)
|
||||
{
|
||||
// make sure target was created (via Target.targetCreated event)
|
||||
if (!\array_key_exists($targetId, $this->targets)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->targets[$targetId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Target[]
|
||||
*/
|
||||
public function getTargets()
|
||||
{
|
||||
return \array_values($this->targets);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $targetId
|
||||
*
|
||||
* @throws CommunicationException
|
||||
*
|
||||
* @return Page|null
|
||||
*/
|
||||
public function getPage($targetId)
|
||||
{
|
||||
if (\array_key_exists($targetId, $this->pages)) {
|
||||
return $this->pages[$targetId];
|
||||
}
|
||||
|
||||
$target = $this->getTarget($targetId);
|
||||
|
||||
if ('page' !== $target->getTargetInfo('type')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get initial frame tree
|
||||
$frameTreeResponse = $target->getSession()->sendMessageSync(new Message('Page.getFrameTree'));
|
||||
|
||||
// make sure frame tree was found
|
||||
if (!$frameTreeResponse->isSuccessful()) {
|
||||
throw new ResponseHasError('Cannot read frame tree. Please, consider upgrading chrome version.');
|
||||
}
|
||||
|
||||
// create page
|
||||
$page = new Page($target, $frameTreeResponse['result']['frameTree']);
|
||||
|
||||
// Page.enable
|
||||
$page->getSession()->sendMessageSync(new Message('Page.enable'));
|
||||
|
||||
// Network.enable
|
||||
$page->getSession()->sendMessageSync(new Message('Network.enable'));
|
||||
|
||||
// Runtime.enable
|
||||
$page->getSession()->sendMessageSync(new Message('Runtime.enable'));
|
||||
|
||||
// Page.setLifecycleEventsEnabled
|
||||
$page->getSession()->sendMessageSync(new Message('Page.setLifecycleEventsEnabled', ['enabled' => true]));
|
||||
|
||||
// set up http headers
|
||||
$headers = $this->connection->getConnectionHttpHeaders();
|
||||
|
||||
if (\count($headers) > 0) {
|
||||
$page->setExtraHTTPHeaders($headers);
|
||||
}
|
||||
|
||||
// add prescript
|
||||
if ($this->pagePreScript) {
|
||||
$page->addPreScript($this->pagePreScript);
|
||||
}
|
||||
|
||||
$this->pages[$targetId] = $page;
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Page[]
|
||||
*/
|
||||
public function getPages()
|
||||
{
|
||||
$ids = \array_keys($this->targets);
|
||||
|
||||
$pages = \array_filter(\array_map([$this, 'getPage'], $ids));
|
||||
|
||||
return \array_values($pages);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,467 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Browser;
|
||||
|
||||
use HeadlessChromium\Browser;
|
||||
use HeadlessChromium\Communication\Connection;
|
||||
use HeadlessChromium\Exception\OperationTimedOut;
|
||||
use HeadlessChromium\Utils;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Wrench\Exception\SocketException;
|
||||
|
||||
/**
|
||||
* A browser process starter. Don't use directly, use BrowserFactory instead.
|
||||
*/
|
||||
class BrowserProcess implements LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* chrome instance's user data data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $userDataDir;
|
||||
|
||||
/**
|
||||
* @var Process
|
||||
*/
|
||||
protected $process;
|
||||
|
||||
/**
|
||||
* True if the user data dir is temporary and should be deleted on process closes.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $userDataDirIsTemp;
|
||||
|
||||
/**
|
||||
* @var Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var ProcessAwareBrowser
|
||||
*/
|
||||
protected $browser;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $wasKilled = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $wasStarted = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $wsUri;
|
||||
|
||||
/**
|
||||
* BrowserProcess constructor.
|
||||
*
|
||||
* @param LoggerInterface|null $logger
|
||||
*/
|
||||
public function __construct(LoggerInterface $logger = null)
|
||||
{
|
||||
// set or create logger
|
||||
$this->setLogger($logger ?? new NullLogger());
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the browser.
|
||||
*
|
||||
* @param string $binary
|
||||
* @param array $options
|
||||
*/
|
||||
public function start($binary, $options): void
|
||||
{
|
||||
if ($this->wasStarted) {
|
||||
// cannot start twice because once started this class contains the necessary data to cleanup the browser.
|
||||
// starting in again would result in replacing those data.
|
||||
throw new \RuntimeException('This process was already started');
|
||||
}
|
||||
|
||||
$this->wasStarted = true;
|
||||
|
||||
// log
|
||||
$this->logger->debug('process: initializing');
|
||||
|
||||
// user data dir
|
||||
if (!\array_key_exists('userDataDir', $options) || !$options['userDataDir']) {
|
||||
// if no data dir specified create it
|
||||
$options['userDataDir'] = $this->createTempDir();
|
||||
|
||||
// set user data dir to get removed on close
|
||||
$this->userDataDirIsTemp = true;
|
||||
}
|
||||
$this->userDataDir = $options['userDataDir'];
|
||||
|
||||
// log
|
||||
$this->logger->debug('process: using directory: '.$options['userDataDir']);
|
||||
|
||||
// get args for command line
|
||||
$args = $this->getArgsFromOptions($binary, $options);
|
||||
|
||||
// setup chrome process
|
||||
if (!\array_key_exists('keepAlive', $options) || !$options['keepAlive']) {
|
||||
$process = new Process($args, null, $options['envVariables'] ?? null);
|
||||
} else {
|
||||
$process = new ProcessKeepAlive($args, null, $options['envVariables'] ?? null);
|
||||
}
|
||||
$this->process = $process;
|
||||
|
||||
// log
|
||||
$this->logger->debug('process: starting process: '.$process->getCommandLine());
|
||||
|
||||
// and start
|
||||
$process->start();
|
||||
|
||||
// wait for start and retrieve ws uri
|
||||
$startupTimeout = $options['startupTimeout'] ?? 30;
|
||||
$this->wsUri = $this->waitForStartup($process, $startupTimeout * 1000 * 1000);
|
||||
|
||||
// log
|
||||
$this->logger->debug('process: connecting using '.$this->wsUri);
|
||||
|
||||
// connect to browser
|
||||
$connection = new Connection($this->wsUri, $this->logger, $options['sendSyncDefaultTimeout'] ?? 5000);
|
||||
$connection->connect();
|
||||
|
||||
// connection delay
|
||||
if (\array_key_exists('connectionDelay', $options)) {
|
||||
$connection->setConnectionDelay($options['connectionDelay']);
|
||||
}
|
||||
|
||||
// connection headers
|
||||
if (\array_key_exists('headers', $options)) {
|
||||
$connection->setConnectionHttpHeaders($options['headers']);
|
||||
}
|
||||
|
||||
// set connection to allow killing chrome
|
||||
$this->connection = $connection;
|
||||
|
||||
// create browser instance
|
||||
$this->browser = new ProcessAwareBrowser($connection, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ProcessAwareBrowser
|
||||
*/
|
||||
public function getBrowser()
|
||||
{
|
||||
return $this->browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSocketUri()
|
||||
{
|
||||
return $this->wsUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kills the process and clean temporary files.
|
||||
*
|
||||
* @throws OperationTimedOut
|
||||
*/
|
||||
public function kill(): void
|
||||
{
|
||||
// log
|
||||
$this->logger->debug('process: killing chrome');
|
||||
|
||||
if ($this->wasKilled) {
|
||||
// log
|
||||
$this->logger->debug('process: chrome already killed, ignoring');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->wasKilled = true;
|
||||
|
||||
if (isset($this->process)) {
|
||||
// close gracefully if connection exists
|
||||
if (isset($this->connection)) {
|
||||
// if socket connect try graceful close
|
||||
if ($this->connection->isConnected()) {
|
||||
// first try to close with Browser.close
|
||||
// if Browser.close is not implemented, try to kill by closing all pages
|
||||
try {
|
||||
// log
|
||||
$this->logger->debug('process: trying to close chrome gracefully');
|
||||
$this->browser->sendCloseMessage();
|
||||
} catch (\Exception $e) {
|
||||
// log
|
||||
$this->logger->debug('process: closing chrome gracefully - compatibility');
|
||||
|
||||
// close all pages if connected
|
||||
try {
|
||||
$this->connection->isConnected() && Utils::closeAllPage($this->connection);
|
||||
} catch (OperationTimedOut $e) {
|
||||
// log
|
||||
$this->logger->debug('process: failed to close all pages');
|
||||
}
|
||||
}
|
||||
|
||||
// disconnect socket
|
||||
try {
|
||||
$this->connection->disconnect();
|
||||
} catch (SocketException $e) {
|
||||
// Socket might be already disconnected
|
||||
}
|
||||
|
||||
// log
|
||||
$this->logger->debug('process: waiting for process to close');
|
||||
|
||||
// wait for process to close
|
||||
$generator = function (Process $process) {
|
||||
while ($process->isRunning()) {
|
||||
yield 2 * 1000; // wait for 2ms
|
||||
}
|
||||
};
|
||||
$timeout = 8 * 1000 * 1000; // 8 seconds
|
||||
|
||||
try {
|
||||
Utils::tryWithTimeout($timeout, $generator($this->process));
|
||||
} catch (OperationTimedOut $e) {
|
||||
// log
|
||||
$this->logger->debug('process: process didn\'t close by itself');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stop process if running
|
||||
if ($this->process->isRunning()) {
|
||||
// log
|
||||
$this->logger->debug('process: stopping process');
|
||||
|
||||
// stop process
|
||||
$exitCode = $this->process->stop();
|
||||
|
||||
// log
|
||||
$this->logger->debug('process: process stopped with exit code '.$exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
// remove data dir
|
||||
if ($this->userDataDirIsTemp && $this->userDataDir) {
|
||||
try {
|
||||
// log
|
||||
$this->logger->debug('process: cleaning temporary resources:'.$this->userDataDir);
|
||||
|
||||
// cleaning
|
||||
$fs = new Filesystem();
|
||||
$fs->remove($this->userDataDir);
|
||||
} catch (\Exception $e) {
|
||||
// log
|
||||
$this->logger->debug('process: ✗ could not clean temporary resources');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get args for creating chrome's startup command.
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getArgsFromOptions($binary, array $options)
|
||||
{
|
||||
// command line args to add to start chrome (inspired by puppeteer configs)
|
||||
// see https://peter.sh/experiments/chromium-command-line-switches/
|
||||
$args = [
|
||||
$binary,
|
||||
|
||||
// auto debug port
|
||||
'--remote-debugging-port=0',
|
||||
|
||||
// disable undesired features
|
||||
'--disable-background-networking',
|
||||
'--disable-background-timer-throttling',
|
||||
'--disable-client-side-phishing-detection',
|
||||
'--disable-hang-monitor',
|
||||
'--disable-popup-blocking',
|
||||
'--disable-prompt-on-repost',
|
||||
'--disable-sync',
|
||||
'--disable-translate',
|
||||
'--disable-features=ChromeWhatsNewUI',
|
||||
'--metrics-recording-only',
|
||||
'--no-first-run',
|
||||
'--safebrowsing-disable-auto-update',
|
||||
|
||||
// automation mode
|
||||
'--enable-automation',
|
||||
|
||||
// password settings
|
||||
'--password-store=basic',
|
||||
'--use-mock-keychain', // osX only
|
||||
];
|
||||
|
||||
// enable headless mode
|
||||
if (!\array_key_exists('headless', $options) || $options['headless']) {
|
||||
$args[] = '--headless';
|
||||
$args[] = '--disable-gpu';
|
||||
$args[] = '--font-render-hinting=none';
|
||||
$args[] = '--hide-scrollbars';
|
||||
$args[] = '--mute-audio';
|
||||
}
|
||||
|
||||
// disable loading of images (currently can't be done via devtools, only CLI)
|
||||
if (\array_key_exists('enableImages', $options) && (false === $options['enableImages'])) {
|
||||
$args[] = '--blink-settings=imagesEnabled=false';
|
||||
}
|
||||
|
||||
// window's size
|
||||
if (\array_key_exists('windowSize', $options) && $options['windowSize']) {
|
||||
if (
|
||||
!\is_array($options['windowSize']) ||
|
||||
2 !== \count($options['windowSize']) ||
|
||||
!\is_numeric($options['windowSize'][0]) ||
|
||||
!\is_numeric($options['windowSize'][1])
|
||||
) {
|
||||
throw new \InvalidArgumentException('Option "windowSize" must be an array of dimensions (eg: [1000, 1200])');
|
||||
}
|
||||
|
||||
$args[] = '--window-size='.\implode(',', $options['windowSize']);
|
||||
}
|
||||
|
||||
// sandbox mode - useful if you want to use chrome headless inside docker
|
||||
if (\array_key_exists('noSandbox', $options) && $options['noSandbox']) {
|
||||
$args[] = '--no-sandbox';
|
||||
}
|
||||
|
||||
// user agent
|
||||
if (\array_key_exists('userAgent', $options)) {
|
||||
$args[] = '--user-agent='.$options['userAgent'];
|
||||
}
|
||||
|
||||
// ignore certificate errors
|
||||
if (\array_key_exists('ignoreCertificateErrors', $options) && $options['ignoreCertificateErrors']) {
|
||||
$args[] = '--ignore-certificate-errors';
|
||||
}
|
||||
|
||||
// proxy server
|
||||
if (\array_key_exists('proxyServer', $options)) {
|
||||
$args[] = '--proxy-server='.$options['proxyServer'];
|
||||
}
|
||||
if (\array_key_exists('noProxyServer', $options) && $options['noProxyServer']) {
|
||||
$args[] = '--no-proxy-server';
|
||||
}
|
||||
if (\array_key_exists('proxyBypassList', $options)) {
|
||||
$args[] = '--proxy-bypass-list='.$options['proxyBypassList'];
|
||||
}
|
||||
|
||||
// add custom flags
|
||||
if (\array_key_exists('customFlags', $options) && \is_array($options['customFlags'])) {
|
||||
$args = \array_merge($args, $options['customFlags']);
|
||||
}
|
||||
|
||||
// add user data dir to args
|
||||
$args[] = '--user-data-dir='.$options['userDataDir'];
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for chrome to startup (given a process) and return the ws uri to connect to.
|
||||
*
|
||||
* @param Process $process
|
||||
* @param int $timeout
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function waitForStartup(Process $process, int $timeout)
|
||||
{
|
||||
// log
|
||||
$this->logger->debug('process: waiting for '.$timeout / 1000000 .' seconds for startup');
|
||||
|
||||
try {
|
||||
$generator = function (Process $process) {
|
||||
while (true) {
|
||||
if (!$process->isRunning()) {
|
||||
// log
|
||||
$this->logger->debug('process: ✗ chrome process stopped');
|
||||
|
||||
// exception
|
||||
$message = 'Chrome process stopped before startup completed.';
|
||||
$error = \trim($process->getErrorOutput());
|
||||
if (!empty($error)) {
|
||||
$message .= ' Additional info: '.$error;
|
||||
}
|
||||
throw new \RuntimeException($message);
|
||||
}
|
||||
|
||||
$output = \trim($process->getIncrementalErrorOutput());
|
||||
|
||||
if ($output) {
|
||||
// log
|
||||
$this->logger->debug('process: chrome output:'.$output);
|
||||
|
||||
$outputs = \explode(\PHP_EOL, $output);
|
||||
|
||||
foreach ($outputs as $output) {
|
||||
$output = \trim($output);
|
||||
|
||||
// ignore empty line
|
||||
if (empty($output)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// find socket uri
|
||||
if (\preg_match('/DevTools listening on (ws:\/\/.*)/', $output, $matches)) {
|
||||
// log
|
||||
$this->logger->debug('process: ✓ accepted output');
|
||||
|
||||
return $matches[1];
|
||||
} else {
|
||||
// log
|
||||
$this->logger->debug('process: ignoring output:'.\trim($output));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wait for 10ms
|
||||
yield 10 * 1000;
|
||||
}
|
||||
};
|
||||
|
||||
return Utils::tryWithTimeout($timeout, $generator($process));
|
||||
} catch (OperationTimedOut $e) {
|
||||
throw new \RuntimeException('Cannot start browser', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temp directory for the app.
|
||||
*
|
||||
* @return string path to the new temp directory
|
||||
*/
|
||||
private function createTempDir()
|
||||
{
|
||||
$tmpFile = \tempnam(\sys_get_temp_dir(), 'chromium-php-');
|
||||
|
||||
\unlink($tmpFile);
|
||||
\mkdir($tmpFile);
|
||||
|
||||
return $tmpFile;
|
||||
}
|
||||
}
|
46
pandora_console/vendor/chrome-php/chrome/src/Browser/ProcessAwareBrowser.php
vendored
Normal file
46
pandora_console/vendor/chrome-php/chrome/src/Browser/ProcessAwareBrowser.php
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Browser;
|
||||
|
||||
use HeadlessChromium\Browser;
|
||||
use HeadlessChromium\Communication\Connection;
|
||||
|
||||
class ProcessAwareBrowser extends Browser
|
||||
{
|
||||
/**
|
||||
* @var BrowserProcess
|
||||
*/
|
||||
protected $browserProcess;
|
||||
|
||||
public function __construct(Connection $connection, BrowserProcess $browserProcess)
|
||||
{
|
||||
parent::__construct($connection);
|
||||
|
||||
$this->browserProcess = $browserProcess;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->browserProcess->kill();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSocketUri()
|
||||
{
|
||||
return $this->browserProcess->getSocketUri();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Browser;
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class ProcessKeepAlive extends Process
|
||||
{
|
||||
public function __destruct()
|
||||
{
|
||||
// Do nothing because we are in mode keep alive, default behavior is to kill the process
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium;
|
||||
|
||||
use HeadlessChromium\Browser\BrowserProcess;
|
||||
use HeadlessChromium\Browser\ProcessAwareBrowser;
|
||||
use HeadlessChromium\Communication\Connection;
|
||||
use HeadlessChromium\Exception\BrowserConnectionFailed;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Wrench\Exception\HandshakeException;
|
||||
|
||||
class BrowserFactory
|
||||
{
|
||||
protected $chromeBinary;
|
||||
|
||||
/**
|
||||
* Options for browser creation.
|
||||
*
|
||||
* - connectionDelay: Delay to apply between each operation for debugging purposes (default: none)
|
||||
* - customFlags: An array of flags to pass to the command line.
|
||||
* - debugLogger: A string (e.g "php://stdout"), or resource, or PSR-3 logger instance to print debug messages (default: none)
|
||||
* - enableImages: Toggles loading of images (default: true)
|
||||
* - envVariables: An array of environment variables to pass to the process (example DISPLAY variable)
|
||||
* - headers: An array of custom HTTP headers
|
||||
* - headless: Enable or disable headless mode (default: true)
|
||||
* - ignoreCertificateErrors: Set chrome to ignore ssl errors
|
||||
* - keepAlive: Set to `true` to keep alive the chrome instance when the script terminates (default: false)
|
||||
* - noSandbox: Enable no sandbox mode, useful to run in a docker container (default: false)
|
||||
* - proxyServer: Proxy server to use. ex: `127.0.0.1:8080` (default: none)
|
||||
* - sendSyncDefaultTimeout: Default timeout (ms) for sending sync messages (default 5000 ms)
|
||||
* - startupTimeout: Maximum time in seconds to wait for chrome to start (default: 30 sec)
|
||||
* - userAgent: User agent to use for the whole browser
|
||||
* - userDataDir: Chrome user data dir (default: a new empty dir is generated temporarily)
|
||||
* - windowSize: Size of the window. ex: `[1920, 1080]` (default: none)
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
public function __construct(string $chromeBinary = null)
|
||||
{
|
||||
$this->chromeBinary = $chromeBinary ?? (new AutoDiscover())->guessChromeBinaryPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a chrome process and allows to interact with it.
|
||||
*
|
||||
* @see BrowserFactory::$options
|
||||
*
|
||||
* @param array|null $options overwrite options for browser creation
|
||||
*
|
||||
* @return ProcessAwareBrowser a Browser instance to interact with the new chrome process
|
||||
*/
|
||||
public function createBrowser(?array $options = null): ProcessAwareBrowser
|
||||
{
|
||||
$options = $options ?? $this->options;
|
||||
|
||||
// create logger from options
|
||||
$logger = self::createLogger($options);
|
||||
|
||||
// create browser process
|
||||
$browserProcess = new BrowserProcess($logger);
|
||||
|
||||
// instruct the runtime to kill chrome and clean temp files on exit
|
||||
if (!\array_key_exists('keepAlive', $options) || !$options['keepAlive']) {
|
||||
\register_shutdown_function([$browserProcess, 'kill']);
|
||||
}
|
||||
|
||||
// start the browser and connect to it
|
||||
$browserProcess->start($this->chromeBinary, $options);
|
||||
|
||||
return $browserProcess->getBrowser();
|
||||
}
|
||||
|
||||
public function addHeader(string $name, string $value): void
|
||||
{
|
||||
$this->options['headers'][$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $headers
|
||||
*/
|
||||
public function addHeaders(array $headers): void
|
||||
{
|
||||
foreach ($headers as $name => $value) {
|
||||
$this->addHeader($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to an existing browser using it's web socket uri.
|
||||
*
|
||||
* usage:
|
||||
*
|
||||
* ```
|
||||
* $browserFactory = new BrowserFactory();
|
||||
* $browser = $browserFactory->createBrowser();
|
||||
*
|
||||
* $uri = $browser->getSocketUri();
|
||||
*
|
||||
* $existingBrowser = BrowserFactory::connectToBrowser($uri);
|
||||
* ```
|
||||
*
|
||||
* @param string $uri
|
||||
* @param array $options options when creating the connection to the browser:
|
||||
* - connectionDelay: amount of time in seconds to slows down connection for debugging purposes (default: none)
|
||||
* - debugLogger: resource string ("php://stdout"), resource or psr-3 logger instance (default: none)
|
||||
* - sendSyncDefaultTimeout: maximum time in ms to wait for synchronous messages to send (default 5000 ms)
|
||||
*
|
||||
* @throws BrowserConnectionFailed
|
||||
*
|
||||
* @return Browser
|
||||
*/
|
||||
public static function connectToBrowser(string $uri, array $options = []): Browser
|
||||
{
|
||||
$logger = self::createLogger($options);
|
||||
|
||||
if ($logger) {
|
||||
$logger->debug('Browser Factory: connecting using '.$uri);
|
||||
}
|
||||
|
||||
// connect to browser
|
||||
$connection = new Connection($uri, $logger, $options['sendSyncDefaultTimeout'] ?? 5000);
|
||||
|
||||
// try to connect
|
||||
try {
|
||||
$connection->connect();
|
||||
} catch (HandshakeException $e) {
|
||||
throw new BrowserConnectionFailed('Invalid socket uri', 0, $e);
|
||||
}
|
||||
|
||||
// make sure it is connected
|
||||
if (!$connection->isConnected()) {
|
||||
throw new BrowserConnectionFailed('Cannot connect to the browser, make sure it was not closed');
|
||||
}
|
||||
|
||||
// connection delay
|
||||
if (\array_key_exists('connectionDelay', $options)) {
|
||||
$connection->setConnectionDelay($options['connectionDelay']);
|
||||
}
|
||||
|
||||
return new Browser($connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default options to be used in all browser instances.
|
||||
*
|
||||
* @see BrowserFactory::$options
|
||||
*/
|
||||
public function setOptions(array $options): void
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or overwrite options to the default options list.
|
||||
*
|
||||
* @see BrowserFactory::$options
|
||||
*/
|
||||
public function addOptions(array $options): void
|
||||
{
|
||||
$this->options = \array_merge($this->options, $options);
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a logger instance from given options.
|
||||
*/
|
||||
private static function createLogger(array $options): LoggerInterface
|
||||
{
|
||||
$logger = $options['debugLogger'] ?? null;
|
||||
|
||||
if ($logger instanceof LoggerInterface) {
|
||||
return $logger;
|
||||
}
|
||||
|
||||
if (\is_string($logger) || \is_resource($logger)) {
|
||||
$log = new Logger('chrome');
|
||||
$log->pushHandler(new StreamHandler($logger));
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
return new NullLogger();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium;
|
||||
|
||||
class Clip
|
||||
{
|
||||
protected $x;
|
||||
protected $y;
|
||||
protected $height;
|
||||
protected $width;
|
||||
protected $scale;
|
||||
|
||||
/**
|
||||
* Clip constructor.
|
||||
*
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param int $height
|
||||
* @param int $width
|
||||
* @param float $scale
|
||||
*/
|
||||
public function __construct($x, $y, $width, $height, $scale = 1.0)
|
||||
{
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
$this->height = $height;
|
||||
$this->width = $width;
|
||||
$this->scale = $scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getX()
|
||||
{
|
||||
return $this->x;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getY()
|
||||
{
|
||||
return $this->y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getScale()
|
||||
{
|
||||
return $this->scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $x
|
||||
*/
|
||||
public function setX($x): void
|
||||
{
|
||||
$this->x = $x;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $y
|
||||
*/
|
||||
public function setY($y): void
|
||||
{
|
||||
$this->y = $y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $height
|
||||
*/
|
||||
public function setHeight($height): void
|
||||
{
|
||||
$this->height = $height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $width
|
||||
*/
|
||||
public function setWidth($width): void
|
||||
{
|
||||
$this->width = $width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $scale
|
||||
*/
|
||||
public function setScale($scale): void
|
||||
{
|
||||
$this->scale = $scale;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,458 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Communication;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use HeadlessChromium\Communication\Socket\SocketInterface;
|
||||
use HeadlessChromium\Communication\Socket\Wrench;
|
||||
use HeadlessChromium\Exception\CommunicationException;
|
||||
use HeadlessChromium\Exception\CommunicationException\CannotReadResponse;
|
||||
use HeadlessChromium\Exception\CommunicationException\InvalidResponse;
|
||||
use HeadlessChromium\Exception\OperationTimedOut;
|
||||
use HeadlessChromium\Exception\TargetDestroyed;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Wrench\Client as WrenchBaseClient;
|
||||
|
||||
class Connection extends EventEmitter implements LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
public const EVENT_TARGET_CREATED = 'method:Target.targetCreated';
|
||||
public const EVENT_TARGET_INFO_CHANGED = 'method:Target.targetInfoChanged';
|
||||
public const EVENT_TARGET_DESTROYED = 'method:Target.targetDestroyed';
|
||||
|
||||
/**
|
||||
* When strict mode is enabled communication error will result in exceptions.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $strict = true;
|
||||
|
||||
/**
|
||||
* time in ms to wait between each message to be sent
|
||||
* That helps to see what is happening when debugging.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $delay;
|
||||
|
||||
/**
|
||||
* time in ms when the previous message was sent. Used to know how long to wait for before send next message
|
||||
* (only when $delay is set).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $lastMessageSentTime;
|
||||
|
||||
/**
|
||||
* @var SocketInterface
|
||||
*/
|
||||
protected $wsClient;
|
||||
|
||||
/**
|
||||
* List of response sent from the remote host and that are waiting to be read.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $responseBuffer = [];
|
||||
|
||||
/**
|
||||
* Default timeout for send sync in ms.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $sendSyncDefaultTimeout;
|
||||
|
||||
/**
|
||||
* @var Session[]
|
||||
*/
|
||||
protected $sessions = [];
|
||||
|
||||
/**
|
||||
* @var array array of data received and waiting to be read
|
||||
*/
|
||||
protected $receivedData = [];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $httpHeaders = [];
|
||||
|
||||
/**
|
||||
* CommunicationChannel constructor.
|
||||
*
|
||||
* @param SocketInterface|string $socketClient
|
||||
* @param int|null $sendSyncDefaultTimeout
|
||||
*/
|
||||
public function __construct($socketClient, LoggerInterface $logger = null, int $sendSyncDefaultTimeout = null)
|
||||
{
|
||||
// set or create logger
|
||||
$this->setLogger($logger ?? new NullLogger());
|
||||
|
||||
// set timeout
|
||||
$this->sendSyncDefaultTimeout = $sendSyncDefaultTimeout ?? 5000;
|
||||
|
||||
// create socket client
|
||||
if (\is_string($socketClient)) {
|
||||
$socketClient = new Wrench(new WrenchBaseClient($socketClient, 'http://127.0.0.1'), $this->logger);
|
||||
} elseif (!\is_object($socketClient) && !$socketClient instanceof SocketInterface) {
|
||||
throw new \InvalidArgumentException('$socketClient param should be either a SockInterface instance or a web socket uri string');
|
||||
}
|
||||
|
||||
$this->wsClient = $socketClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LoggerInterface
|
||||
*/
|
||||
public function getLogger(): LoggerInterface
|
||||
{
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the delay to apply everytime before data are sent.
|
||||
*
|
||||
* @param int $delay
|
||||
*/
|
||||
public function setConnectionDelay(int $delay): void
|
||||
{
|
||||
$this->delay = $delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $headers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setConnectionHttpHeaders(array $headers): void
|
||||
{
|
||||
$this->httpHeaders = $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getConnectionHttpHeaders(): array
|
||||
{
|
||||
return $this->httpHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default timeout used when sending a message synchronously.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSendSyncDefaultTimeout(): int
|
||||
{
|
||||
return $this->sendSyncDefaultTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isStrict(): bool
|
||||
{
|
||||
return $this->strict;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $strict
|
||||
*/
|
||||
public function setStrict(bool $strict): void
|
||||
{
|
||||
$this->strict = $strict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the server.
|
||||
*
|
||||
* @return bool Whether a new connection was made
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
return $this->wsClient->connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the underlying socket, and marks the client as disconnected.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
return $this->wsClient->disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the client is currently connected.
|
||||
*
|
||||
* @return bool true if connected
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
return $this->wsClient->isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait before sending next message.
|
||||
*/
|
||||
private function waitForDelay(): void
|
||||
{
|
||||
if ($this->lastMessageSentTime) {
|
||||
$currentTime = (int) (\hrtime(true) / 1000 / 1000);
|
||||
// if not enough time was spent until last message was sent, wait
|
||||
if ($this->lastMessageSentTime + $this->delay > $currentTime) {
|
||||
$timeToWait = ($this->lastMessageSentTime + $this->delay) - $currentTime;
|
||||
\usleep($timeToWait * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
$this->lastMessageSentTime = (int) (\hrtime(true) / 1000 / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the given message and returns a response reader.
|
||||
*
|
||||
* @param Message $message
|
||||
*
|
||||
* @throws CommunicationException
|
||||
*
|
||||
* @return ResponseReader
|
||||
*/
|
||||
public function sendMessage(Message $message): ResponseReader
|
||||
{
|
||||
// if delay enabled wait before sending message
|
||||
if ($this->delay > 0) {
|
||||
$this->waitForDelay();
|
||||
}
|
||||
|
||||
$sent = $this->wsClient->sendData((string) $message);
|
||||
|
||||
if (!$sent) {
|
||||
$message = 'Message could not be sent.';
|
||||
|
||||
if (!$this->isConnected()) {
|
||||
$message .= ' Reason: the connection is closed.';
|
||||
} else {
|
||||
$message .= ' Reason: unknown.';
|
||||
}
|
||||
|
||||
throw new CommunicationException($message);
|
||||
}
|
||||
|
||||
return new ResponseReader($message, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @param int|null $timeout
|
||||
*
|
||||
* @throws OperationTimedOut
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function sendMessageSync(Message $message, int $timeout = null): Response
|
||||
{
|
||||
$responseReader = $this->sendMessage($message);
|
||||
$response = $responseReader->waitForResponse($timeout ?? $this->sendSyncDefaultTimeout);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a session for the given target id.
|
||||
*
|
||||
* @param string $targetId
|
||||
* @param ?string $sessionId
|
||||
*
|
||||
* @return Session
|
||||
*/
|
||||
public function createSession($targetId, $sessionId = null): Session
|
||||
{
|
||||
if (null === $sessionId) {
|
||||
$response = $this->sendMessageSync(
|
||||
new Message('Target.attachToTarget', ['targetId' => $targetId, 'flatten' => true])
|
||||
);
|
||||
if (empty($response['result'])) {
|
||||
throw new TargetDestroyed('The target was destroyed.');
|
||||
}
|
||||
$sessionId = $response['result']['sessionId'];
|
||||
}
|
||||
$session = new Session($targetId, $sessionId, $this);
|
||||
|
||||
$this->sessions[$sessionId] = $session;
|
||||
|
||||
$session->on('destroyed', function () use ($sessionId): void {
|
||||
$this->logger->debug('✘ session('.$sessionId.') was destroyed and unreferenced.');
|
||||
unset($this->sessions[$sessionId]);
|
||||
});
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive and stack data from the socket.
|
||||
*/
|
||||
private function receiveData(): void
|
||||
{
|
||||
$this->receivedData = \array_merge($this->receivedData, $this->wsClient->receiveData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from CRI and store messages.
|
||||
*
|
||||
* @throws CannotReadResponse
|
||||
* @throws InvalidResponse
|
||||
*
|
||||
* @return bool true if data were received
|
||||
*/
|
||||
public function readData()
|
||||
{
|
||||
$hasData = false;
|
||||
|
||||
while ($this->readLine()) {
|
||||
$hasData = true;
|
||||
}
|
||||
|
||||
return $hasData;
|
||||
}
|
||||
|
||||
public function readLine()
|
||||
{
|
||||
// if buffer empty, then read from input
|
||||
if (empty($this->receivedData)) {
|
||||
$this->receiveData();
|
||||
}
|
||||
|
||||
// dispatch first line of buffer
|
||||
$datum = \array_shift($this->receivedData);
|
||||
if ($datum) {
|
||||
return $this->dispatchMessage($datum);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the message and either stores the response or emits an event.
|
||||
*
|
||||
* @throws InvalidResponse
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
private function dispatchMessage(string $message, Session $session = null)
|
||||
{
|
||||
// responses come as json string
|
||||
$response = \json_decode($message, true);
|
||||
|
||||
// if json not valid throw exception
|
||||
$jsonError = \json_last_error();
|
||||
if (\JSON_ERROR_NONE !== $jsonError) {
|
||||
if ($this->isStrict()) {
|
||||
throw new CannotReadResponse(\sprintf('Response from chrome remote interface is not a valid json response. JSON error: %s', $jsonError));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// response must be array
|
||||
if (!\is_array($response)) {
|
||||
if ($this->isStrict()) {
|
||||
throw new CannotReadResponse('Response from chrome remote interface was not a valid array');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// id is required to identify the response
|
||||
if (!isset($response['id'])) {
|
||||
if (isset($response['method'])) {
|
||||
if ('Target.receivedMessageFromTarget' == $response['method']) {
|
||||
$session = $this->sessions[$response['params']['sessionId']];
|
||||
|
||||
return $this->dispatchMessage($response['params']['message'], $session);
|
||||
} else {
|
||||
if (!$session && isset($response['sessionId'])) {
|
||||
$session = $this->sessions[$response['sessionId']] ?? null;
|
||||
}
|
||||
if ($session) {
|
||||
$this->logger->debug(
|
||||
'session('.$session->getSessionId().'): ⇶ dispatching method:'.$response['method']
|
||||
);
|
||||
$session->emit('method:'.$response['method'], [$response['params']]);
|
||||
} else {
|
||||
$this->logger->debug('connection: ⇶ dispatching method:'.$response['method']);
|
||||
$this->emit('method:'.$response['method'], [$response['params']]);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isStrict()) {
|
||||
throw new InvalidResponse('Response from chrome remote interface did not provide a valid message id');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// store response
|
||||
$this->responseBuffer[$response['id']] = $response;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if a response for the given id exists.
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasResponseForId($id)
|
||||
{
|
||||
return \array_key_exists($id, $this->responseBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getResponseForId($id)
|
||||
{
|
||||
if (\array_key_exists($id, $this->responseBuffer)) {
|
||||
$data = $this->responseBuffer[$id];
|
||||
unset($this->responseBuffer[$id]);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sessionId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSessionDestroyed($sessionId)
|
||||
{
|
||||
return !isset($this->sessions[$sessionId]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Communication;
|
||||
|
||||
class Message
|
||||
{
|
||||
/**
|
||||
* global message id auto incremented for each message sent.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $messageId = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $method;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $params;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
protected $sessionId;
|
||||
|
||||
/**
|
||||
* get the last generated message id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getLastMessageId()
|
||||
{
|
||||
return self::$messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct(string $method, array $params = [], ?string $sessionId = null)
|
||||
{
|
||||
$this->id = ++self::$messageId;
|
||||
$this->method = $method;
|
||||
$this->params = $params;
|
||||
$this->sessionId = $sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMethod(): string
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getParams(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$message = [
|
||||
'id' => $this->getId(),
|
||||
'method' => $this->getMethod(),
|
||||
'params' => (object) $this->getParams(),
|
||||
];
|
||||
if (null !== $this->sessionId) {
|
||||
$message['sessionId'] = $this->sessionId;
|
||||
}
|
||||
|
||||
return \json_encode($message);
|
||||
}
|
||||
|
||||
public function getSessionId(): ?string
|
||||
{
|
||||
return $this->sessionId;
|
||||
}
|
||||
|
||||
public function setSessionId(string $sessionId): void
|
||||
{
|
||||
$this->sessionId = $sessionId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Communication;
|
||||
|
||||
class Response implements \ArrayAccess
|
||||
{
|
||||
protected $message;
|
||||
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Response constructor.
|
||||
*/
|
||||
public function __construct(array $data, Message $message)
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the response is error free.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSuccessful()
|
||||
{
|
||||
return !\array_key_exists('error', $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error message if set.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getErrorMessage(bool $extended = true)
|
||||
{
|
||||
$message = [];
|
||||
|
||||
if ($extended && isset($this->data['error']['code'])) {
|
||||
$message[] = $this->data['error']['code'];
|
||||
}
|
||||
|
||||
if (isset($this->data['error']['message'])) {
|
||||
$message[] = $this->data['error']['message'];
|
||||
}
|
||||
|
||||
if ($extended && isset($this->data['error']['data']) && \is_string($this->data['error']['data'])) {
|
||||
$message[] = $this->data['error']['data'];
|
||||
}
|
||||
|
||||
return \implode(' - ', $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error code if set.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getErrorCode()
|
||||
{
|
||||
return $this->data['error']['code'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResultData($name)
|
||||
{
|
||||
return $this->data['result'][$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Message
|
||||
*/
|
||||
public function getMessage(): Message
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* The data returned by chrome dev tools.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return \array_key_exists($offset, $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->data[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
throw new \Exception('Responses are immutable');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
throw new \Exception('Responses are immutable');
|
||||
}
|
||||
}
|
188
pandora_console/vendor/chrome-php/chrome/src/Communication/ResponseReader.php
vendored
Normal file
188
pandora_console/vendor/chrome-php/chrome/src/Communication/ResponseReader.php
vendored
Normal file
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Communication;
|
||||
|
||||
use HeadlessChromium\Exception\NoResponseAvailable;
|
||||
use HeadlessChromium\Exception\OperationTimedOut;
|
||||
use HeadlessChromium\Utils;
|
||||
|
||||
class ResponseReader
|
||||
{
|
||||
/**
|
||||
* @var Message
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* @var Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var Response|null
|
||||
*/
|
||||
protected $response = null;
|
||||
|
||||
/**
|
||||
* Response constructor.
|
||||
*
|
||||
* @param Message $message
|
||||
* @param Connection $connection
|
||||
*/
|
||||
public function __construct(Message $message, Connection $connection)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if a response is available.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasResponse()
|
||||
{
|
||||
return null !== $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* the message to get a response for.
|
||||
*
|
||||
* @return Message
|
||||
*/
|
||||
public function getMessage(): Message
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* The connection to check messages for.
|
||||
*
|
||||
* @return Connection
|
||||
*/
|
||||
public function getConnection(): Connection
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response.
|
||||
*
|
||||
* Note: response will always be missing until checkForResponse is called
|
||||
* and the response is available in the buffer
|
||||
*
|
||||
* @throws NoResponseAvailable
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getResponse(): Response
|
||||
{
|
||||
if (!$this->response) {
|
||||
throw new NoResponseAvailable('Response is not available. Try to use the method waitForResponse instead.');
|
||||
}
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a response.
|
||||
*
|
||||
* @param int $timeout time to wait for a response (milliseconds)
|
||||
*
|
||||
* @throws NoResponseAvailable
|
||||
* @throws OperationTimedOut
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function waitForResponse(int $timeout = null): Response
|
||||
{
|
||||
if ($this->hasResponse()) {
|
||||
return $this->getResponse();
|
||||
}
|
||||
|
||||
// default 2000ms
|
||||
$timeout = $timeout ?? 2000;
|
||||
|
||||
return Utils::tryWithTimeout($timeout * 1000, $this->waitForResponseGenerator());
|
||||
}
|
||||
|
||||
/**
|
||||
* To be used in waitForResponse method.
|
||||
*
|
||||
* @throws NoResponseAvailable
|
||||
*
|
||||
* @return \Generator|Response
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
private function waitForResponseGenerator()
|
||||
{
|
||||
while (true) {
|
||||
// 50 microseconds between each iteration
|
||||
$tryDelay = 50;
|
||||
|
||||
// read available response
|
||||
$hasResponse = $this->checkForResponse();
|
||||
|
||||
// if found return it
|
||||
if ($hasResponse) {
|
||||
return $this->getResponse();
|
||||
}
|
||||
|
||||
// wait before next check
|
||||
yield $tryDelay;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check in the connection if a response exists for the message and store it if the response exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkForResponse()
|
||||
{
|
||||
// if response is already read, ignore
|
||||
if ($this->hasResponse()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = $this->message->getId();
|
||||
|
||||
// if response exists store it
|
||||
if ($this->connection->hasResponseForId($id)) {
|
||||
$this->response = new Response($this->connection->getResponseForId($id), $this->message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// read data
|
||||
while (!$this->connection->hasResponseForId($id)) {
|
||||
if (!$this->connection->readLine()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if response store it
|
||||
if ($this->connection->hasResponseForId($id)) {
|
||||
$this->response = new Response($this->connection->getResponseForId($id), $this->message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if the session was destroyed in the mean time
|
||||
if (null !== $this->message->getSessionId() && $this->connection->isSessionDestroyed($this->message->getSessionId())) {
|
||||
throw new \HeadlessChromium\Exception\TargetDestroyed('The session is destroyed.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Communication;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use HeadlessChromium\Exception\CommunicationException;
|
||||
use HeadlessChromium\Exception\NoResponseAvailable;
|
||||
use HeadlessChromium\Exception\TargetDestroyed;
|
||||
|
||||
class Session extends EventEmitter
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sessionId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $targetId;
|
||||
|
||||
/**
|
||||
* @var Connection|null
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $destroyed = false;
|
||||
|
||||
/**
|
||||
* Session constructor.
|
||||
*
|
||||
* @param string $targetId
|
||||
* @param string $sessionId
|
||||
* @param Connection $connection
|
||||
*/
|
||||
public function __construct(string $targetId, string $sessionId, Connection $connection)
|
||||
{
|
||||
$this->sessionId = $sessionId;
|
||||
$this->targetId = $targetId;
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
*
|
||||
* @throws CommunicationException
|
||||
*
|
||||
* @return ResponseReader
|
||||
*/
|
||||
public function sendMessage(Message $message): ResponseReader
|
||||
{
|
||||
if ($this->destroyed) {
|
||||
throw new TargetDestroyed('The session was destroyed.');
|
||||
}
|
||||
|
||||
if (null === $message->getSessionId()) {
|
||||
$message->setSessionId($this->getSessionId());
|
||||
}
|
||||
$topResponse = $this->getConnection()->sendMessage($message);
|
||||
|
||||
return $topResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @param int $timeout
|
||||
*
|
||||
* @throws NoResponseAvailable
|
||||
* @throws CommunicationException
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function sendMessageSync(Message $message, int $timeout = null): Response
|
||||
{
|
||||
$responseReader = $this->sendMessage($message);
|
||||
|
||||
$response = $responseReader->waitForResponse($timeout ?? $this->getConnection()->getSendSyncDefaultTimeout());
|
||||
|
||||
if (!$response) {
|
||||
throw new NoResponseAvailable('No response was sent in the given timeout');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSessionId()
|
||||
{
|
||||
return $this->sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTargetId()
|
||||
{
|
||||
return $this->targetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Connection
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
if ($this->destroyed) {
|
||||
throw new TargetDestroyed('The session was destroyed.');
|
||||
}
|
||||
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the session as destroyed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
if ($this->destroyed) {
|
||||
throw new TargetDestroyed('The session was already destroyed.');
|
||||
}
|
||||
$this->emit('destroyed');
|
||||
$this->connection = null;
|
||||
$this->destroyed = true;
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
}
|
127
pandora_console/vendor/chrome-php/chrome/src/Communication/Socket/MockSocket.php
vendored
Normal file
127
pandora_console/vendor/chrome-php/chrome/src/Communication/Socket/MockSocket.php
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Communication\Socket;
|
||||
|
||||
/**
|
||||
* A mock adapter for unit tests.
|
||||
*/
|
||||
class MockSocket implements SocketInterface
|
||||
{
|
||||
protected $sentData = [];
|
||||
|
||||
protected $receivedData = [];
|
||||
protected $receivedDataForNextMessage = [];
|
||||
|
||||
protected $isConnected = false;
|
||||
|
||||
protected $shouldConnect = true;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sendData($data)
|
||||
{
|
||||
if (!$this->isConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->sentData[] = $data;
|
||||
|
||||
if (!empty($this->receivedDataForNextMessage)) {
|
||||
$data = \json_decode($data, true);
|
||||
|
||||
if ($data['id']) {
|
||||
$next = \array_shift($this->receivedDataForNextMessage);
|
||||
$next = \json_decode($next, true);
|
||||
$next['id'] = $data['id'];
|
||||
$this->receivedData[] = \json_encode($next);
|
||||
|
||||
if (isset($data['method']) && 'Target.sendMessageToTarget' == $data['method']) {
|
||||
--$next['id'];
|
||||
$this->receivedData[] = \json_encode($next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* resets the data stored with sendData.
|
||||
*/
|
||||
public function flushData(): void
|
||||
{
|
||||
$this->sentData = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the data stored with sendData.
|
||||
*/
|
||||
public function getSentData()
|
||||
{
|
||||
return $this->sentData;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function receiveData(): array
|
||||
{
|
||||
$data = $this->receivedData;
|
||||
$this->receivedData = [];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data to be returned with receiveData.
|
||||
*
|
||||
* @param bool $forNextMessage true to set the response id automatically
|
||||
* for next message (can stack for multiple messages
|
||||
*/
|
||||
public function addReceivedData($data, $forNextMessage = false): void
|
||||
{
|
||||
if ($forNextMessage) {
|
||||
$this->receivedDataForNextMessage[] = $data;
|
||||
} else {
|
||||
$this->receivedData[] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
$this->isConnected = $this->shouldConnect;
|
||||
|
||||
return $this->isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
return $this->isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function disconnect($reason = 1000)
|
||||
{
|
||||
$this->isConnected = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
55
pandora_console/vendor/chrome-php/chrome/src/Communication/Socket/SocketInterface.php
vendored
Normal file
55
pandora_console/vendor/chrome-php/chrome/src/Communication/Socket/SocketInterface.php
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Communication\Socket;
|
||||
|
||||
/**
|
||||
* A simplified interface to wrap a socket client.
|
||||
*/
|
||||
interface SocketInterface
|
||||
{
|
||||
/**
|
||||
* Sends data to the socket.
|
||||
*
|
||||
* @return bool whether the data were sent
|
||||
*/
|
||||
public function sendData($data);
|
||||
|
||||
/**
|
||||
* Receives data sent by the server.
|
||||
*
|
||||
* @return array Payload received since the last call to receive()
|
||||
*/
|
||||
public function receiveData(): array;
|
||||
|
||||
/**
|
||||
* Connect to the server.
|
||||
*
|
||||
* @return bool Whether a new connection was made
|
||||
*/
|
||||
public function connect();
|
||||
|
||||
/**
|
||||
* Whether the client is currently connected.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isConnected();
|
||||
|
||||
/**
|
||||
* Disconnects the underlying socket, and marks the client as disconnected.
|
||||
*
|
||||
* @param int $reason see http://tools.ietf.org/html/rfc6455#section-7.4
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disconnect($reason = 1000);
|
||||
}
|
140
pandora_console/vendor/chrome-php/chrome/src/Communication/Socket/Wrench.php
vendored
Normal file
140
pandora_console/vendor/chrome-php/chrome/src/Communication/Socket/Wrench.php
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Communication\Socket;
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Wrench\Client as WrenchClient;
|
||||
use Wrench\Payload\Payload;
|
||||
|
||||
class Wrench implements SocketInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* An auto incremented counter to uniquely identify each socket instance.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $socketIdCounter = 0;
|
||||
|
||||
/**
|
||||
* @var WrenchClient
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* Id of this socket generated from self::$socketIdCounter.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $socketId = 0;
|
||||
|
||||
/**
|
||||
* @param WrenchClient $client
|
||||
*/
|
||||
public function __construct(WrenchClient $client, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
|
||||
$this->setLogger($logger ?? new NullLogger());
|
||||
|
||||
$this->socketId = ++self::$socketIdCounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sendData($data)
|
||||
{
|
||||
// log
|
||||
$this->logger->debug('socket('.$this->socketId.'): → sending data:'.$data);
|
||||
|
||||
// send data
|
||||
return $this->client->sendData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function receiveData(): array
|
||||
{
|
||||
$playloads = $this->client->receive();
|
||||
|
||||
$data = [];
|
||||
|
||||
if ($playloads) {
|
||||
foreach ($playloads as $playload) {
|
||||
/** @var Payload */
|
||||
$dataString = $playload->getPayload();
|
||||
$data[] = $dataString;
|
||||
|
||||
// log
|
||||
$this->logger->debug('socket('.$this->socketId.'): ← receiving data:'.$dataString);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
// log
|
||||
$this->logger->debug('socket('.$this->socketId.'): connecting');
|
||||
|
||||
$connected = $this->client->connect();
|
||||
|
||||
if ($connected) {
|
||||
// log
|
||||
$this->logger->debug('socket('.$this->socketId.'): ✓ connected');
|
||||
} else {
|
||||
// log
|
||||
$this->logger->debug('socket('.$this->socketId.'): ✗ could not connect');
|
||||
}
|
||||
|
||||
return $connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
return $this->client->isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function disconnect($reason = 1000)
|
||||
{
|
||||
// log
|
||||
$this->logger->debug('socket('.$this->socketId.'): disconnecting');
|
||||
|
||||
$disconnected = $this->client->disconnect($reason);
|
||||
|
||||
if ($disconnected) {
|
||||
// log
|
||||
$this->logger->debug('socket('.$this->socketId.'): ✓ disconnected');
|
||||
} else {
|
||||
// log
|
||||
$this->logger->debug('socket('.$this->socketId.'): ✗ could not disconnect');
|
||||
}
|
||||
|
||||
return $disconnected;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Communication;
|
||||
|
||||
use HeadlessChromium\Exception\TargetDestroyed;
|
||||
|
||||
class Target
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $targetInfo;
|
||||
|
||||
/**
|
||||
* @var Session|null
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* @var Connection
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $destroyed = false;
|
||||
|
||||
/**
|
||||
* Target constructor.
|
||||
*/
|
||||
public function __construct(array $targetInfo, Connection $connection)
|
||||
{
|
||||
$this->targetInfo = $targetInfo;
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string $sessionId
|
||||
*
|
||||
* @return Session
|
||||
*/
|
||||
public function getSession(?string $sessionId = null): Session
|
||||
{
|
||||
if ($this->destroyed) {
|
||||
throw new TargetDestroyed('The target was destroyed.');
|
||||
}
|
||||
|
||||
// if not already done, create a session for the target
|
||||
if (!$this->session) {
|
||||
$this->session = $this->connection->createSession($this->getTargetInfo('targetId'), $sessionId);
|
||||
}
|
||||
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the target as destroyed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
if ($this->destroyed) {
|
||||
throw new TargetDestroyed('The target was already destroyed.');
|
||||
}
|
||||
|
||||
if ($this->session) {
|
||||
$this->session->destroy();
|
||||
$this->session = null;
|
||||
}
|
||||
|
||||
$this->destroyed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDestroyed(): bool
|
||||
{
|
||||
return $this->destroyed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get target info value by it's name or null if it does not exist.
|
||||
*
|
||||
* @param string $infoName
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getTargetInfo($infoName)
|
||||
{
|
||||
return $this->targetInfo[$infoName] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when Target.targetInfoChanged is triggered.
|
||||
*
|
||||
* @param array $targetInfo
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function targetInfoChanged($targetInfo): void
|
||||
{
|
||||
$this->targetInfo = $targetInfo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Cookies;
|
||||
|
||||
class Cookie implements \ArrayAccess
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Cookie constructor.
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
if (isset($data['expires']) && \is_string($data['expires']) && !\is_numeric($data['expires'])) {
|
||||
$data['expires'] = \strtotime($data['expires']);
|
||||
}
|
||||
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->offsetGet('value');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->offsetGet('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDomain()
|
||||
{
|
||||
return $this->offsetGet('domain');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return \array_key_exists($offset, $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->data[$offset] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
throw new \RuntimeException('Cannot set cookie values');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
throw new \RuntimeException('Cannot unset cookie values');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param array $params
|
||||
*
|
||||
* @return Cookie
|
||||
*/
|
||||
public static function create($name, $value, array $params = [])
|
||||
{
|
||||
$params['name'] = $name;
|
||||
$params['value'] = $value;
|
||||
|
||||
return new self($params);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Cookies;
|
||||
|
||||
class CookiesCollection implements \IteratorAggregate, \Countable
|
||||
{
|
||||
/**
|
||||
* @var Cookie[]
|
||||
*/
|
||||
protected $cookies = [];
|
||||
|
||||
/**
|
||||
* CookiesCollection constructor.
|
||||
*/
|
||||
public function __construct(array $cookies = null)
|
||||
{
|
||||
if ($cookies) {
|
||||
foreach ($cookies as $cookie) {
|
||||
if (\is_array($cookie)) {
|
||||
$cookie = new Cookie($cookie);
|
||||
}
|
||||
$this->addCookie($cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a cookie.
|
||||
*/
|
||||
public function addCookie(Cookie $cookie): void
|
||||
{
|
||||
$this->cookies[] = $cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->cookies);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return \count($this->cookies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie at the given index.
|
||||
*
|
||||
* @param int $i
|
||||
*
|
||||
* @return Cookie
|
||||
*/
|
||||
public function getAt($i): Cookie
|
||||
{
|
||||
if (!isset($this->cookies[$i])) {
|
||||
throw new \RuntimeException(\sprintf('No cookie at index %s', $i));
|
||||
}
|
||||
|
||||
return $this->cookies[$i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find cookies with matching values.
|
||||
*
|
||||
* usage:
|
||||
*
|
||||
* ```
|
||||
* // find cookies having name == 'foo'
|
||||
* $newCookies = $cookies->filterBy('name', 'foo');
|
||||
*
|
||||
* // find cookies having domain == 'example.com'
|
||||
* $newCookies = $cookies->filterBy('domain', 'example.com');
|
||||
* ```
|
||||
*
|
||||
* @param string $param
|
||||
* @param string $value
|
||||
*
|
||||
* @return CookiesCollection
|
||||
*/
|
||||
public function filterBy(string $param, string $value)
|
||||
{
|
||||
return new self(\array_filter($this->cookies, function (Cookie $cookie) use ($param, $value) {
|
||||
return $cookie[$param] == $value;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find first cookies with matching value.
|
||||
*
|
||||
* usage:
|
||||
*
|
||||
* ```
|
||||
* // find first cookie having name == 'foo'
|
||||
* $cookie = $cookies->findOneBy('name', 'foo');
|
||||
*
|
||||
* if ($cookie) {
|
||||
* // do something
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param string $param
|
||||
* @param string $value
|
||||
*
|
||||
* @return Cookie|null
|
||||
*/
|
||||
public function findOneBy(string $param, string $value)
|
||||
{
|
||||
foreach ($this->cookies as $cookie) {
|
||||
if ($cookie[$param] == $value) {
|
||||
return $cookie;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace HeadlessChromium\Dom;
|
||||
|
||||
use HeadlessChromium\Communication\Message;
|
||||
use HeadlessChromium\Page;
|
||||
|
||||
class Dom extends Node
|
||||
{
|
||||
public function __construct(Page $page)
|
||||
{
|
||||
$message = new Message('DOM.getDocument');
|
||||
$stream = $page->getSession()->sendMessage($message);
|
||||
$response = $stream->waitForResponse(1000);
|
||||
|
||||
$rootNodeId = $response->getResultData('root')['nodeId'];
|
||||
|
||||
parent::__construct($page, $rootNodeId);
|
||||
}
|
||||
|
||||
public function search(string $selector): array
|
||||
{
|
||||
$message = new Message('DOM.performSearch', [
|
||||
'query' => $selector,
|
||||
]);
|
||||
$response = $this->page->getSession()->sendMessageSync($message);
|
||||
|
||||
$this->assertNotError($response);
|
||||
|
||||
$searchId = $response->getResultData('searchId');
|
||||
$count = $response->getResultData('resultCount');
|
||||
|
||||
if (0 === $count) {
|
||||
return [];
|
||||
}
|
||||
$message = new Message('DOM.getSearchResults', [
|
||||
'searchId' => $searchId,
|
||||
'fromIndex' => 0,
|
||||
'toIndex' => $count,
|
||||
]);
|
||||
|
||||
$response = $this->page->getSession()->sendMessageSync($message);
|
||||
|
||||
$this->assertNotError($response);
|
||||
|
||||
$nodes = [];
|
||||
$nodeIds = $response->getResultData('nodeIds');
|
||||
foreach ($nodeIds as $nodeId) {
|
||||
$nodes[] = new Node($this->page, $nodeId);
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace HeadlessChromium\Dom;
|
||||
|
||||
use HeadlessChromium\Communication\Message;
|
||||
use HeadlessChromium\Communication\Response;
|
||||
use HeadlessChromium\Exception\DomException;
|
||||
use HeadlessChromium\Page;
|
||||
|
||||
class Node
|
||||
{
|
||||
/**
|
||||
* @var Page
|
||||
*/
|
||||
protected $page;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $nodeId;
|
||||
|
||||
public function __construct(Page $page, int $nodeId)
|
||||
{
|
||||
$this->page = $page;
|
||||
$this->nodeId = $nodeId;
|
||||
}
|
||||
|
||||
public function getAttributes(): NodeAttributes
|
||||
{
|
||||
$message = new Message('DOM.getAttributes', [
|
||||
'nodeId' => $this->nodeId,
|
||||
]);
|
||||
$response = $this->page->getSession()->sendMessageSync($message);
|
||||
|
||||
$this->assertNotError($response);
|
||||
|
||||
$attributes = $response->getResultData('attributes');
|
||||
|
||||
return new NodeAttributes($attributes);
|
||||
}
|
||||
|
||||
public function setAttributeValue(string $name, string $value): void
|
||||
{
|
||||
$message = new Message('DOM.setAttributeValue', [
|
||||
'nodeId' => $this->nodeId,
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
]);
|
||||
$response = $this->page->getSession()->sendMessageSync($message);
|
||||
|
||||
$this->assertNotError($response);
|
||||
}
|
||||
|
||||
public function querySelector(string $selector): ?self
|
||||
{
|
||||
$message = new Message('DOM.querySelector', [
|
||||
'nodeId' => $this->nodeId,
|
||||
'selector' => $selector,
|
||||
]);
|
||||
$response = $this->page->getSession()->sendMessageSync($message);
|
||||
$this->assertNotError($response);
|
||||
|
||||
$nodeId = $response->getResultData('nodeId');
|
||||
|
||||
if (null !== $nodeId && 0 !== $nodeId) {
|
||||
return new self($this->page, $nodeId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function querySelectorAll(string $selector): array
|
||||
{
|
||||
$message = new Message('DOM.querySelectorAll', [
|
||||
'nodeId' => $this->nodeId,
|
||||
'selector' => $selector,
|
||||
]);
|
||||
$response = $this->page->getSession()->sendMessageSync($message);
|
||||
|
||||
$this->assertNotError($response);
|
||||
|
||||
$nodes = [];
|
||||
$nodeIds = $response->getResultData('nodeIds');
|
||||
foreach ($nodeIds as $nodeId) {
|
||||
$nodes[] = new self($this->page, $nodeId);
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
public function focus(): void
|
||||
{
|
||||
$message = new Message('DOM.focus', [
|
||||
'nodeId' => $this->nodeId,
|
||||
]);
|
||||
$response = $this->page->getSession()->sendMessageSync($message);
|
||||
|
||||
$this->assertNotError($response);
|
||||
}
|
||||
|
||||
public function getAttribute(string $name): ?string
|
||||
{
|
||||
return $this->getAttributes()->get($name);
|
||||
}
|
||||
|
||||
public function getPosition(): ?NodePosition
|
||||
{
|
||||
$message = new Message('DOM.getBoxModel', [
|
||||
'nodeId' => $this->nodeId,
|
||||
]);
|
||||
$response = $this->page->getSession()->sendMessageSync($message);
|
||||
|
||||
$this->assertNotError($response);
|
||||
|
||||
$points = $response->getResultData('model')['content'];
|
||||
|
||||
if (null !== $points) {
|
||||
return new NodePosition($points);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function hasPosition(): bool
|
||||
{
|
||||
return null !== $this->getPosition();
|
||||
}
|
||||
|
||||
public function getHTML(): string
|
||||
{
|
||||
$message = new Message('DOM.getOuterHTML', [
|
||||
'nodeId' => $this->nodeId,
|
||||
]);
|
||||
$response = $this->page->getSession()->sendMessageSync($message);
|
||||
|
||||
$this->assertNotError($response);
|
||||
|
||||
return $response->getResultData('outerHTML');
|
||||
}
|
||||
|
||||
public function getText(): string
|
||||
{
|
||||
return \strip_tags($this->getHTML());
|
||||
}
|
||||
|
||||
public function scrollIntoView(): void
|
||||
{
|
||||
$message = new Message('DOM.scrollIntoViewIfNeeded', [
|
||||
'nodeId' => $this->nodeId,
|
||||
]);
|
||||
$response = $this->page->getSession()->sendMessageSync($message);
|
||||
|
||||
$this->assertNotError($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DomException
|
||||
*/
|
||||
public function click(): void
|
||||
{
|
||||
if (false === $this->hasPosition()) {
|
||||
throw new DomException('Failed to click element without position');
|
||||
}
|
||||
$this->scrollIntoView();
|
||||
$position = $this->getPosition();
|
||||
$this->page->mouse()
|
||||
->move($position->getCenterX(), $position->getCenterY())
|
||||
->click();
|
||||
}
|
||||
|
||||
public function sendKeys(string $text): void
|
||||
{
|
||||
$this->scrollIntoView();
|
||||
$this->focus();
|
||||
$this->page->keyboard()
|
||||
->typeText($text);
|
||||
}
|
||||
|
||||
public function sendFile(string $filePath): void
|
||||
{
|
||||
$this->sendFiles([$filePath]);
|
||||
}
|
||||
|
||||
public function sendFiles(array $filePaths): void
|
||||
{
|
||||
$message = new Message('DOM.setFileInputFiles', [
|
||||
'files' => $filePaths,
|
||||
'nodeId' => $this->nodeId,
|
||||
]);
|
||||
$response = $this->page->getSession()->sendMessageSync($message);
|
||||
|
||||
$this->assertNotError($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DomException
|
||||
*/
|
||||
public function assertNotError(Response $response): void
|
||||
{
|
||||
if (!$response->isSuccessful()) {
|
||||
throw new DOMException($response->getErrorMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace HeadlessChromium\Dom;
|
||||
|
||||
class NodeAttributes
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $attributes = [];
|
||||
|
||||
public function __construct(array $attrs)
|
||||
{
|
||||
for ($i = 0; $i <= \count($attrs) - 2; $i += 2) {
|
||||
$this->attributes[$attrs[$i]] = $attrs[$i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return isset($this->attributes[$name]);
|
||||
}
|
||||
|
||||
public function get(string $name): ?string
|
||||
{
|
||||
return $this->attributes[$name] ?? null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace HeadlessChromium\Dom;
|
||||
|
||||
class NodePosition
|
||||
{
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $x;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $y;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $width;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $height;
|
||||
|
||||
public function __construct(array $points)
|
||||
{
|
||||
$leftTopX = $points[0];
|
||||
$leftTopY = $points[1];
|
||||
$rightTopX = $points[2];
|
||||
$rightTopY = $points[3];
|
||||
$rightBottomX = $points[4];
|
||||
$rightBottomY = $points[5];
|
||||
$leftBottomX = $points[6];
|
||||
$leftBottomY = $points[7];
|
||||
|
||||
$this->x = $leftTopX;
|
||||
$this->y = $leftTopY;
|
||||
|
||||
$this->height = $leftBottomY - $leftTopY;
|
||||
$this->width = $rightBottomX - $leftBottomX;
|
||||
}
|
||||
|
||||
public function getX(): int
|
||||
{
|
||||
return (int) $this->x;
|
||||
}
|
||||
|
||||
public function getY(): int
|
||||
{
|
||||
return (int) $this->y;
|
||||
}
|
||||
|
||||
public function getWidth(): int
|
||||
{
|
||||
return (int) $this->width;
|
||||
}
|
||||
|
||||
public function getHeight(): int
|
||||
{
|
||||
return (int) $this->height;
|
||||
}
|
||||
|
||||
public function getCenterX(): int
|
||||
{
|
||||
return (int) ($this->x + ($this->width / 2));
|
||||
}
|
||||
|
||||
public function getCenterY(): int
|
||||
{
|
||||
return (int) ($this->y + ($this->height / 2));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace HeadlessChromium\Dom\Selector;
|
||||
|
||||
/**
|
||||
* @see https://developer.mozilla.org/docs/Web/API/Document/querySelector
|
||||
*/
|
||||
final class CssSelector implements Selector
|
||||
{
|
||||
/** @var string */
|
||||
private $expression;
|
||||
|
||||
public function __construct(string $expression)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
}
|
||||
|
||||
public function expressionCount(): string
|
||||
{
|
||||
return \sprintf('document.querySelectorAll("%s").length', $this->expression);
|
||||
}
|
||||
|
||||
public function expressionFindOne(int $position): string
|
||||
{
|
||||
return \sprintf('document.querySelectorAll("%s")[%d]', $this->expression, $position - 1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace HeadlessChromium\Dom\Selector;
|
||||
|
||||
interface Selector
|
||||
{
|
||||
public function expressionCount(): string;
|
||||
|
||||
public function expressionFindOne(int $position): string;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace HeadlessChromium\Dom\Selector;
|
||||
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/XPath/Introduction_to_using_XPath_in_JavaScript
|
||||
*/
|
||||
final class XPathSelector implements Selector
|
||||
{
|
||||
/** @var string */
|
||||
private $expression;
|
||||
|
||||
public function __construct(string $expression)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
}
|
||||
|
||||
public function expressionCount(): string
|
||||
{
|
||||
return \sprintf(
|
||||
'document.evaluate("%s", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotLength',
|
||||
\addslashes($this->expression)
|
||||
);
|
||||
}
|
||||
|
||||
public function expressionFindOne(int $position): string
|
||||
{
|
||||
return \sprintf(
|
||||
'document.evaluate("%s[%d]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue',
|
||||
\addslashes($this->expression),
|
||||
$position
|
||||
);
|
||||
}
|
||||
}
|
16
pandora_console/vendor/chrome-php/chrome/src/Exception/BrowserConnectionFailed.php
vendored
Normal file
16
pandora_console/vendor/chrome-php/chrome/src/Exception/BrowserConnectionFailed.php
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class BrowserConnectionFailed extends \Exception
|
||||
{
|
||||
}
|
16
pandora_console/vendor/chrome-php/chrome/src/Exception/CommunicationException.php
vendored
Normal file
16
pandora_console/vendor/chrome-php/chrome/src/Exception/CommunicationException.php
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class CommunicationException extends \Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception\CommunicationException;
|
||||
|
||||
use HeadlessChromium\Exception\CommunicationException;
|
||||
|
||||
class CannotReadResponse extends CommunicationException
|
||||
{
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception\CommunicationException;
|
||||
|
||||
use HeadlessChromium\Exception\CommunicationException;
|
||||
|
||||
class InvalidResponse extends CommunicationException
|
||||
{
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception\CommunicationException;
|
||||
|
||||
use HeadlessChromium\Exception\CommunicationException;
|
||||
|
||||
class ResponseHasError extends CommunicationException
|
||||
{
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class DomException extends Exception
|
||||
{
|
||||
}
|
16
pandora_console/vendor/chrome-php/chrome/src/Exception/ElementNotFoundException.php
vendored
Normal file
16
pandora_console/vendor/chrome-php/chrome/src/Exception/ElementNotFoundException.php
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class ElementNotFoundException extends \Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class EvaluationFailed extends \Exception
|
||||
{
|
||||
}
|
16
pandora_console/vendor/chrome-php/chrome/src/Exception/FilesystemException.php
vendored
Normal file
16
pandora_console/vendor/chrome-php/chrome/src/Exception/FilesystemException.php
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class FilesystemException extends \Exception
|
||||
{
|
||||
}
|
16
pandora_console/vendor/chrome-php/chrome/src/Exception/InvalidTimezoneId.php
vendored
Normal file
16
pandora_console/vendor/chrome-php/chrome/src/Exception/InvalidTimezoneId.php
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class InvalidTimezoneId extends \Exception
|
||||
{
|
||||
}
|
16
pandora_console/vendor/chrome-php/chrome/src/Exception/JavascriptException.php
vendored
Normal file
16
pandora_console/vendor/chrome-php/chrome/src/Exception/JavascriptException.php
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class JavascriptException extends \Exception
|
||||
{
|
||||
}
|
16
pandora_console/vendor/chrome-php/chrome/src/Exception/NavigationExpired.php
vendored
Normal file
16
pandora_console/vendor/chrome-php/chrome/src/Exception/NavigationExpired.php
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class NavigationExpired extends \Exception
|
||||
{
|
||||
}
|
16
pandora_console/vendor/chrome-php/chrome/src/Exception/NoResponseAvailable.php
vendored
Normal file
16
pandora_console/vendor/chrome-php/chrome/src/Exception/NoResponseAvailable.php
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class NoResponseAvailable extends \Exception
|
||||
{
|
||||
}
|
33
pandora_console/vendor/chrome-php/chrome/src/Exception/OperationTimedOut.php
vendored
Normal file
33
pandora_console/vendor/chrome-php/chrome/src/Exception/OperationTimedOut.php
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class OperationTimedOut extends \Exception
|
||||
{
|
||||
public static function createFromTimeout(int $timeoutMicroSec): self
|
||||
{
|
||||
return new self(\sprintf('Operation timed out after %s.', self::getTimeoutPhrase($timeoutMicroSec)));
|
||||
}
|
||||
|
||||
private static function getTimeoutPhrase(int $timeoutMicroSec): string
|
||||
{
|
||||
if ($timeoutMicroSec > 1000 * 1000) {
|
||||
return \sprintf('%ds', (int) ($timeoutMicroSec / (1000 * 1000)));
|
||||
}
|
||||
|
||||
if ($timeoutMicroSec > 1000) {
|
||||
return \sprintf('%dms', (int) ($timeoutMicroSec / 1000));
|
||||
}
|
||||
|
||||
return \sprintf('%dμs', (int) ($timeoutMicroSec));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class PdfFailed extends \Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class ScreenshotFailed extends \Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Exception;
|
||||
|
||||
class TargetDestroyed extends \RuntimeException
|
||||
{
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium;
|
||||
|
||||
class Frame
|
||||
{
|
||||
public const LIFECYCLE_INIT = 'init';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $frameData;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $lifeCycleEvents = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $latestLoaderId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $frameId;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $executionContextId;
|
||||
|
||||
/**
|
||||
* Frame constructor.
|
||||
*
|
||||
* @param array $frameData
|
||||
*/
|
||||
public function __construct(array $frameData)
|
||||
{
|
||||
$this->frameData = $frameData;
|
||||
$this->latestLoaderId = $frameData['loaderId'];
|
||||
$this->frameId = $frameData['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function onLifecycleEvent(array $params): void
|
||||
{
|
||||
if (self::LIFECYCLE_INIT === $params['name']) {
|
||||
$this->lifeCycleEvents = [];
|
||||
$this->latestLoaderId = $params['loaderId'];
|
||||
$this->frameId = $params['frameId'];
|
||||
}
|
||||
|
||||
$this->lifeCycleEvents[$params['name']] = $params['timestamp'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getExecutionContextId(): int
|
||||
{
|
||||
return $this->executionContextId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $executionContextId
|
||||
*/
|
||||
public function setExecutionContextId(int $executionContextId): void
|
||||
{
|
||||
$this->executionContextId = $executionContextId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFrameId(): string
|
||||
{
|
||||
return $this->frameId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLatestLoaderId(): string
|
||||
{
|
||||
return $this->latestLoaderId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the life cycle events of the frame with the time they occurred at.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLifeCycle(): array
|
||||
{
|
||||
return $this->lifeCycleEvents;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium;
|
||||
|
||||
class FrameManager
|
||||
{
|
||||
/**
|
||||
* @var Page
|
||||
*/
|
||||
protected $page;
|
||||
|
||||
/**
|
||||
* @var Frame[]
|
||||
*/
|
||||
protected $frames = [];
|
||||
|
||||
/**
|
||||
* @var Frame
|
||||
*/
|
||||
protected $mainFrame;
|
||||
|
||||
/**
|
||||
* FrameManager constructor.
|
||||
*/
|
||||
public function __construct(Page $page, array $frameTree)
|
||||
{
|
||||
$this->page = $page;
|
||||
|
||||
if (isset($frameTree['frame'])) {
|
||||
// TODO parse children frames
|
||||
$this->frames[$frameTree['frame']['id']] = new Frame($frameTree['frame']);
|
||||
|
||||
// associate main frame
|
||||
$this->mainFrame = $this->frames[$frameTree['frame']['id']];
|
||||
}
|
||||
|
||||
// TODO listen for frame events
|
||||
|
||||
// update frame on init
|
||||
$this->page->getSession()->on('method:Page.lifecycleEvent', function (array $params): void {
|
||||
if (isset($this->frames[$params['frameId']])) {
|
||||
$frame = $this->frames[$params['frameId']];
|
||||
$frame->onLifecycleEvent($params);
|
||||
}
|
||||
});
|
||||
|
||||
// attach context id to frame
|
||||
$this->page->getSession()->on('method:Runtime.executionContextCreated', function (array $params): void {
|
||||
if (isset($params['context']['auxData']['frameId']) && $params['context']['auxData']['isDefault']) {
|
||||
if ($this->hasFrame($params['context']['auxData']['frameId'])) {
|
||||
$frame = $this->getFrame($params['context']['auxData']['frameId']);
|
||||
$frame->setExecutionContextId($params['context']['id']);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO maybe implement Runtime.executionContextDestroyed and Runtime.executionContextsCleared
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given frame exists.
|
||||
*
|
||||
* @param string $frameId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFrame($frameId): bool
|
||||
{
|
||||
return \array_key_exists($frameId, $this->frames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a frame given its id.
|
||||
*
|
||||
* @param string $frameId
|
||||
*
|
||||
* @return Frame
|
||||
*/
|
||||
public function getFrame($frameId): Frame
|
||||
{
|
||||
if (!isset($this->frames[$frameId])) {
|
||||
throw new \RuntimeException(\sprintf('No such frame "%s"', $frameId));
|
||||
}
|
||||
|
||||
return $this->frames[$frameId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main frame.
|
||||
*
|
||||
* @return Frame
|
||||
*/
|
||||
public function getMainFrame(): Frame
|
||||
{
|
||||
return $this->mainFrame;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Input;
|
||||
|
||||
/**
|
||||
* Holds key constants and their respective bit values.
|
||||
*
|
||||
* @see https://chromedevtools.github.io/devtools-protocol/1-2/Input/
|
||||
*/
|
||||
abstract class Key
|
||||
{
|
||||
public const ALT = 1;
|
||||
public const CONTROL = 2;
|
||||
public const META = 4;
|
||||
public const SHIFT = 8;
|
||||
public const COMMAND = self::META;
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Input;
|
||||
|
||||
use HeadlessChromium\Communication\Message;
|
||||
use HeadlessChromium\Page;
|
||||
|
||||
class Keyboard
|
||||
{
|
||||
use KeyboardKeys;
|
||||
|
||||
/**
|
||||
* @var Page
|
||||
*/
|
||||
protected $page;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $sleep = 0;
|
||||
|
||||
/**
|
||||
* @param Page $page
|
||||
*/
|
||||
public function __construct(Page $page)
|
||||
{
|
||||
$this->page = $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type a text string, char by char, without applying modifiers.
|
||||
*
|
||||
* @param string $text text string to be typed
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function typeText(string $text)
|
||||
{
|
||||
$this->page->assertNotClosed();
|
||||
|
||||
$length = \mb_strlen($text);
|
||||
|
||||
for ($i = 0; $i < $length; ++$i) {
|
||||
$this->page->getSession()->sendMessageSync(new Message('Input.dispatchKeyEvent', [
|
||||
'type' => 'char',
|
||||
'modifiers' => $this->getModifiers(),
|
||||
'text' => \mb_substr($text, $i, 1),
|
||||
]));
|
||||
|
||||
\usleep($this->sleep);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type a raw key using the rawKeyDown event, without sending any codes or modifiers.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```php
|
||||
* $page->keyboard()->typeRawKey('Tab');
|
||||
* ```
|
||||
*
|
||||
* @param string $key single raw key to be typed
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function typeRawKey(string $key): self
|
||||
{
|
||||
$this->page->assertNotClosed();
|
||||
|
||||
$this->onKeyPress($key);
|
||||
|
||||
$this->page->getSession()->sendMessageSync(new Message('Input.dispatchKeyEvent', [
|
||||
'type' => 'rawKeyDown',
|
||||
'key' => $key,
|
||||
]));
|
||||
|
||||
\usleep($this->sleep);
|
||||
|
||||
$this->release($key);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Press and release a single key.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```php
|
||||
* $page->keyboard()->type('a');
|
||||
* ```
|
||||
*
|
||||
* @param string $key single key to be typed
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function type(string $key): self
|
||||
{
|
||||
return $this->press($key)->release($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Press a single key with key codes and modifiers.
|
||||
*
|
||||
* A key can be pressed multiple times sequentially. This is what happens
|
||||
* in a real browser when the user presses and holds down hown the key.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```php
|
||||
* $page->keyboard()->press('Control')->press('c'); // press ctrl + c
|
||||
* ```
|
||||
*
|
||||
* @param string $key single key to be pressed
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function press(string $key): self
|
||||
{
|
||||
$this->page->assertNotClosed();
|
||||
|
||||
$this->onKeyPress($key);
|
||||
|
||||
$this->page->getSession()->sendMessageSync(new Message('Input.dispatchKeyEvent', [
|
||||
'type' => 'keyDown',
|
||||
'modifiers' => $this->getModifiers(),
|
||||
'text' => $key,
|
||||
'key' => $this->getCurrentKey(),
|
||||
'windowsVirtualKeyCode' => $this->getKeyCode(),
|
||||
]));
|
||||
|
||||
\usleep($this->sleep);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a single key.
|
||||
*
|
||||
* A key is released only once, even if it was pressed multiple times.
|
||||
* If no key is given, all pressed keys will be released.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```php
|
||||
* $page->keyboard()->release('Control'); // release Control
|
||||
* $page->keyboard()->release(); // release all
|
||||
* ```
|
||||
*
|
||||
* @param string $key (optional) single key to be released
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function release(string $key = null): self
|
||||
{
|
||||
$this->page->assertNotClosed();
|
||||
|
||||
if (null === $key) {
|
||||
$this->releaseAll();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->onKeyRelease($key);
|
||||
|
||||
$this->page->getSession()->sendMessageSync(new Message('Input.dispatchKeyEvent', [
|
||||
'type' => 'keyUp',
|
||||
'key' => $this->getCurrentKey(),
|
||||
]));
|
||||
|
||||
\usleep($this->sleep);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all pressed keys.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function releaseAll(): self
|
||||
{
|
||||
foreach ($this->pressedKeys as $key => $value) {
|
||||
if (true === $value) {
|
||||
$this->release($key);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time interval between key strokes in milliseconds.
|
||||
*
|
||||
* @param int $milliseconds
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setKeyInterval(int $milliseconds)
|
||||
{
|
||||
if ($milliseconds < 0) {
|
||||
$milliseconds = 0;
|
||||
}
|
||||
|
||||
$this->sleep = $milliseconds * 1000;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Input;
|
||||
|
||||
/**
|
||||
* Translates typed keys to their respective codes.
|
||||
*
|
||||
* @see https://chromedevtools.github.io/devtools-protocol/1-2/Input/
|
||||
*/
|
||||
trait KeyboardKeys
|
||||
{
|
||||
/**
|
||||
* Array of currently pressed keys (keyDown events).
|
||||
*
|
||||
* The elements of this array should be unique. A real keyboard can create several keyDown events
|
||||
* by holding down a key, but only one keyUp event will be sent when the key is released.
|
||||
*/
|
||||
protected $pressedKeys = [];
|
||||
|
||||
/**
|
||||
* Current key as a sanitized string.
|
||||
*
|
||||
* Single letters like "v" must be in uppercase, otherwise key combinations like ctrl + v won't work.
|
||||
*/
|
||||
protected $currentKey = '';
|
||||
|
||||
/**
|
||||
* Bit field representing pressed modifier keys.
|
||||
*/
|
||||
protected $modifiers = 0;
|
||||
|
||||
/**
|
||||
* Aliases for modifier keys, in lowercase.
|
||||
*/
|
||||
protected $keyAliases = [
|
||||
Key::ALT => [
|
||||
'alt',
|
||||
'altgr',
|
||||
'alt gr',
|
||||
],
|
||||
Key::CONTROL => [
|
||||
'control',
|
||||
'ctrl',
|
||||
'ctr',
|
||||
],
|
||||
Key::META => [
|
||||
'meta',
|
||||
'command',
|
||||
'cmd',
|
||||
],
|
||||
Key::SHIFT => [
|
||||
'shift',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Register a pressed key and apply modifiers.
|
||||
*
|
||||
* @param string $key pressed key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function onKeyPress(string $key): void
|
||||
{
|
||||
$this->setCurrentKey($key);
|
||||
|
||||
if (true === $this->isKeyPressed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pressedKeys[$this->currentKey] = true;
|
||||
|
||||
$this->toggleModifierFromKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a released key and remove modifiers.
|
||||
*
|
||||
* @param string $key released key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function onKeyRelease(string $key): void
|
||||
{
|
||||
$this->setCurrentKey($key);
|
||||
|
||||
if (false === $this->isKeyPressed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
unset($this->pressedKeys[$this->currentKey]);
|
||||
|
||||
$this->toggleModifierFromKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the current key against the list of aliases.
|
||||
* If it match, try to add or remove its bits to the modifier.
|
||||
*
|
||||
* @see self::$keyAliases
|
||||
* @see self::$modifiers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function toggleModifierFromKey(): void
|
||||
{
|
||||
$key = \strtolower($this->currentKey);
|
||||
|
||||
foreach ($this->keyAliases as $modifier => $aliases) {
|
||||
if (true === \in_array($key, $aliases)) {
|
||||
$this->toggleModifier($modifier);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform bit operations to add or remove bits from the modifier.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* 0001
|
||||
* | 0100
|
||||
* = 0101
|
||||
*
|
||||
* 0101
|
||||
* & 0100
|
||||
* = 0100
|
||||
*
|
||||
* 0101
|
||||
* & 0010
|
||||
* = 0000
|
||||
*
|
||||
* @see self::$modifiers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function toggleModifier(int $bit): void
|
||||
{
|
||||
if (($this->modifiers & $bit) === $bit) {
|
||||
$this->modifiers &= ~$bit;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->modifiers |= $bit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current key was pressed and not released yet.
|
||||
*
|
||||
* @return bool true if they key is listed as pressed
|
||||
*/
|
||||
protected function isKeyPressed(): bool
|
||||
{
|
||||
return \array_key_exists($this->currentKey, $this->pressedKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current key code.
|
||||
*
|
||||
* @return int the key code
|
||||
*/
|
||||
public function getKeyCode(): int
|
||||
{
|
||||
return \ord($this->currentKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current bit modifier.
|
||||
* The browser expects to receive this value as int.
|
||||
*
|
||||
* @return int current bit modifier
|
||||
*/
|
||||
public function getModifiers(): int
|
||||
{
|
||||
return $this->modifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current key being processed.
|
||||
*
|
||||
* @return string the current key
|
||||
*/
|
||||
public function getCurrentKey(): string
|
||||
{
|
||||
return $this->currentKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of unique pressed keys that were not released yet.
|
||||
*
|
||||
* @return array list of pressed keys
|
||||
*/
|
||||
public function getPressedKeys(): array
|
||||
{
|
||||
return $this->pressedKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a key as the current key.
|
||||
*
|
||||
* Single character keys must be in uppercase, otherwhie things like ctrl + v won't work.
|
||||
* Triming the string will also prevent future mistakes during normal usage.
|
||||
*
|
||||
* @param string $key key to be set as current
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setCurrentKey(string $key): void
|
||||
{
|
||||
$this->currentKey = \ucfirst(\trim($key));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,392 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\Input;
|
||||
|
||||
use HeadlessChromium\Communication\Message;
|
||||
use HeadlessChromium\Dom\Selector\CssSelector;
|
||||
use HeadlessChromium\Dom\Selector\Selector;
|
||||
use HeadlessChromium\Exception\ElementNotFoundException;
|
||||
use HeadlessChromium\Exception\JavascriptException;
|
||||
use HeadlessChromium\Page;
|
||||
use HeadlessChromium\Utils;
|
||||
|
||||
class Mouse
|
||||
{
|
||||
public const BUTTON_LEFT = 'left';
|
||||
public const BUTTON_NONE = 'none';
|
||||
public const BUTTON_RIGHT = 'right';
|
||||
public const BUTTON_MIDDLE = 'middle';
|
||||
|
||||
/**
|
||||
* @var Page
|
||||
*/
|
||||
protected $page;
|
||||
|
||||
protected $x = 0;
|
||||
protected $y = 0;
|
||||
|
||||
protected $button = self::BUTTON_NONE;
|
||||
|
||||
/**
|
||||
* @param Page $page
|
||||
*/
|
||||
public function __construct(Page $page)
|
||||
{
|
||||
$this->page = $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param array|null $options
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function move(int $x, int $y, array $options = null)
|
||||
{
|
||||
$this->page->assertNotClosed();
|
||||
|
||||
// get origin of the move
|
||||
$originX = $this->x;
|
||||
$originY = $this->y;
|
||||
|
||||
// set new position after move
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
|
||||
// number of steps to achieve the move
|
||||
$steps = $options['steps'] ?? 1;
|
||||
if ($steps <= 0) {
|
||||
throw new \InvalidArgumentException('options "steps" for mouse move must be a positive integer');
|
||||
}
|
||||
|
||||
// move
|
||||
for ($i = 1; $i <= $steps; ++$i) {
|
||||
$this->page->getSession()->sendMessageSync(new Message('Input.dispatchMouseEvent', [
|
||||
'x' => $originX + ($this->x - $originX) * ($i / $steps),
|
||||
'y' => $originY + ($this->y - $originY) * ($i / $steps),
|
||||
'type' => 'mouseMoved',
|
||||
]));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*/
|
||||
public function press(array $options = null)
|
||||
{
|
||||
$this->page->assertNotClosed();
|
||||
$this->page->getSession()->sendMessageSync(new Message('Input.dispatchMouseEvent', [
|
||||
'x' => $this->x,
|
||||
'y' => $this->y,
|
||||
'type' => 'mousePressed',
|
||||
'button' => $options['button'] ?? self::BUTTON_LEFT,
|
||||
'clickCount' => 1,
|
||||
]));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*/
|
||||
public function release(array $options = null)
|
||||
{
|
||||
$this->page->assertNotClosed();
|
||||
$this->page->getSession()->sendMessageSync(new Message('Input.dispatchMouseEvent', [
|
||||
'x' => $this->x,
|
||||
'y' => $this->y,
|
||||
'type' => 'mouseReleased',
|
||||
'button' => $options['button'] ?? self::BUTTON_LEFT,
|
||||
'clickCount' => 1,
|
||||
]));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $options
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*/
|
||||
public function click(array $options = null)
|
||||
{
|
||||
$this->press($options);
|
||||
$this->release($options);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll up using the mouse wheel.
|
||||
*
|
||||
* @param int $distance Distance in pixels
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function scrollUp(int $distance)
|
||||
{
|
||||
return $this->scroll((-1 * \abs($distance)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll down using the mouse wheel.
|
||||
*
|
||||
* @param int $distance Distance in pixels
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function scrollDown(int $distance)
|
||||
{
|
||||
return $this->scroll(\abs($distance));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll a positive or negative distance using the mouseWheel event type.
|
||||
*
|
||||
* @param int $distanceY Distance in pixels for the Y axis
|
||||
* @param int $distanceX (optional) Distance in pixels for the X axis
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\OperationTimedOut
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
private function scroll(int $distanceY, int $distanceX = 0): self
|
||||
{
|
||||
$this->page->assertNotClosed();
|
||||
|
||||
$scrollableArea = $this->page->getLayoutMetrics()->getCssContentSize();
|
||||
$visibleArea = $this->page->getLayoutMetrics()->getCssVisualViewport();
|
||||
|
||||
$maximumX = $scrollableArea['width'] - $visibleArea['clientWidth'];
|
||||
$maximumY = $scrollableArea['height'] - $visibleArea['clientHeight'];
|
||||
|
||||
$distanceX = $this->getMaximumDistance($distanceX, $visibleArea['pageX'], $maximumX);
|
||||
$distanceY = $this->getMaximumDistance($distanceY, $visibleArea['pageY'], $maximumY);
|
||||
|
||||
$targetX = $visibleArea['pageX'] + $distanceX;
|
||||
$targetY = $visibleArea['pageY'] + $distanceY;
|
||||
|
||||
// make sure the mouse is on the screen
|
||||
$this->move($this->x, $this->y);
|
||||
|
||||
// scroll
|
||||
$this->page->getSession()->sendMessageSync(new Message('Input.dispatchMouseEvent', [
|
||||
'type' => 'mouseWheel',
|
||||
'x' => $this->x,
|
||||
'y' => $this->y,
|
||||
'deltaX' => $distanceX,
|
||||
'deltaY' => $distanceY,
|
||||
]));
|
||||
|
||||
// wait until the scroll is done
|
||||
Utils::tryWithTimeout(30000 * 1000, $this->waitForScroll($targetX, $targetY));
|
||||
|
||||
// set new position after move
|
||||
$this->x += $distanceX;
|
||||
$this->y += $distanceY;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll in both X and Y axis until the given boundaries fit in the screen.
|
||||
*
|
||||
* This method currently scrolls only to right and bottom. If the desired element is outside the visible screen
|
||||
* to the left or top, thie method will not work. Its visibility will stay private until it works for both cases.
|
||||
*
|
||||
* @param int $right The element right boundary
|
||||
* @param int $bottom The element bottom boundary
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
private function scrollToBoundary(int $right, int $bottom): self
|
||||
{
|
||||
$visibleArea = $this->page->getLayoutMetrics()->getCssLayoutViewport();
|
||||
|
||||
$distanceX = $distanceY = 0;
|
||||
|
||||
if ($right > $visibleArea['clientWidth']) {
|
||||
$distanceX = $right - $visibleArea['clientWidth'];
|
||||
}
|
||||
|
||||
if ($bottom > $visibleArea['clientHeight']) {
|
||||
$distanceY = $bottom - $visibleArea['clientHeight'];
|
||||
}
|
||||
|
||||
return $this->scroll($distanceY, $distanceX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an element and move the mouse to a random position over it.
|
||||
*
|
||||
* The search could result in several elements. The $position param can be used to select a specific element.
|
||||
* The given position can only be between 1 and the maximum number or elements. It will be adjusted to the
|
||||
* minimum and maximum values if needed.
|
||||
*
|
||||
* Example:
|
||||
* $page->mouse()->find('#a'):
|
||||
* $page->mouse()->find('.a', 2);
|
||||
*
|
||||
* @see https://developer.mozilla.org/docs/Web/API/Document/querySelector
|
||||
*
|
||||
* @param string $selectors selectors to use with document.querySelector
|
||||
* @param int $position (optional) which element of the result set should be used
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\ElementNotFoundException
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function find(string $selectors, int $position = 1): self
|
||||
{
|
||||
$this->findElement(new CssSelector($selectors), $position);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an element and move the mouse to a random position over it.
|
||||
*
|
||||
* The search could result in several elements. The $position param can be used to select a specific element.
|
||||
* The given position can only be between 1 and the maximum number or elements. It will be adjusted to the
|
||||
* minimum and maximum values if needed.
|
||||
*
|
||||
* Example:
|
||||
* $page->mouse()->findElement(new CssSelector('#a')):
|
||||
* $page->mouse()->findElement(new CssSelector('.a'), 2);
|
||||
* $page->mouse()->findElement(new XPathSelector('//*[@id="a"]'), 2);
|
||||
*
|
||||
* @param Selector $selector selector to use
|
||||
* @param int $position (optional) which element of the result set should be used
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\ElementNotFoundException
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function findElement(Selector $selector, int $position = 1): self
|
||||
{
|
||||
$this->page->assertNotClosed();
|
||||
|
||||
try {
|
||||
$element = Utils::getElementPositionFromPage($this->page, $selector, $position);
|
||||
} catch (JavascriptException $exception) {
|
||||
throw new ElementNotFoundException('The search for "'.$selector->expressionCount().'" returned no result.');
|
||||
}
|
||||
|
||||
if (false === \array_key_exists('x', $element)) {
|
||||
throw new ElementNotFoundException('The search for "'.$selector->expressionFindOne($position).'" returned an element with no position.');
|
||||
}
|
||||
|
||||
$rightBoundary = \floor($element['right']);
|
||||
$bottomBoundary = \floor($element['bottom']);
|
||||
|
||||
$this->scrollToBoundary($rightBoundary, $bottomBoundary);
|
||||
|
||||
$visibleArea = $this->page->getLayoutMetrics()->getLayoutViewport();
|
||||
|
||||
$offsetX = $visibleArea['pageX'];
|
||||
$offsetY = $visibleArea['pageY'];
|
||||
$minX = $element['left'] - $offsetX;
|
||||
$minY = $element['top'] - $offsetY;
|
||||
|
||||
$positionX = \floor($minX + (($rightBoundary - $offsetX) - $minX) / 2);
|
||||
$positionY = \ceil($minY + (($bottomBoundary - $offsetY) - $minY) / 2);
|
||||
|
||||
$this->move($positionX, $positionY);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum distance to scroll a page.
|
||||
*
|
||||
* @param int $distance Distance to scroll, positive or negative
|
||||
* @param int $current Current position
|
||||
* @param int $maximum Maximum possible distance
|
||||
*
|
||||
* @return int allowed distance to scroll
|
||||
*/
|
||||
private function getMaximumDistance(int $distance, int $current, int $maximum): int
|
||||
{
|
||||
$result = $current + $distance;
|
||||
|
||||
if ($result < 0) {
|
||||
return $distance + \abs($result);
|
||||
}
|
||||
|
||||
if ($result > $maximum) {
|
||||
return $maximum - $current;
|
||||
}
|
||||
|
||||
return $distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the browser to process the scroll command.
|
||||
*
|
||||
* Return the number of microseconds to wait before trying again or true in case of success.
|
||||
*
|
||||
* @see \HeadlessChromium\Utils::tryWithTimeout
|
||||
*
|
||||
* @param int $targetX
|
||||
* @param int $targetY
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\OperationTimedOut
|
||||
*
|
||||
* @return bool|\Generator
|
||||
*/
|
||||
private function waitForScroll(int $targetX, int $targetY)
|
||||
{
|
||||
while (true) {
|
||||
$visibleArea = $this->page->getLayoutMetrics()->getCssVisualViewport();
|
||||
|
||||
if ($visibleArea['pageX'] === $targetX && $visibleArea['pageY'] === $targetY) {
|
||||
return true;
|
||||
}
|
||||
|
||||
yield 1000;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current mouse position.
|
||||
*
|
||||
* @return array [x, y]
|
||||
*/
|
||||
public function getPosition(): array
|
||||
{
|
||||
return [
|
||||
'x' => $this->x,
|
||||
'y' => $this->y,
|
||||
];
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
104
pandora_console/vendor/chrome-php/chrome/src/PageUtils/AbstractBinaryInput.php
vendored
Normal file
104
pandora_console/vendor/chrome-php/chrome/src/PageUtils/AbstractBinaryInput.php
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\PageUtils;
|
||||
|
||||
use HeadlessChromium\Communication\ResponseReader;
|
||||
use HeadlessChromium\Exception\FilesystemException;
|
||||
use HeadlessChromium\Exception\ScreenshotFailed;
|
||||
|
||||
abstract class AbstractBinaryInput
|
||||
{
|
||||
/**
|
||||
* @var ResponseReader
|
||||
*/
|
||||
protected $responseReader;
|
||||
|
||||
/**
|
||||
* @param ResponseReader $responseReader
|
||||
*/
|
||||
public function __construct(ResponseReader $responseReader)
|
||||
{
|
||||
$this->responseReader = $responseReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseReader
|
||||
*/
|
||||
public function getResponseReader(): ResponseReader
|
||||
{
|
||||
return $this->responseReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base64 representation of the file.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getBase64(int $timeout = null)
|
||||
{
|
||||
$response = $this->responseReader->waitForResponse($timeout);
|
||||
|
||||
if (!$response->isSuccessful()) {
|
||||
throw $this->getException($response->getErrorMessage());
|
||||
}
|
||||
|
||||
return $response->getResultData('data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save data to the given file.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @throws FilesystemException
|
||||
* @throws ScreenshotFailed
|
||||
*/
|
||||
public function saveToFile(string $path, int $timeout = 5000): void
|
||||
{
|
||||
$response = $this->responseReader->waitForResponse($timeout);
|
||||
|
||||
if (!$response->isSuccessful()) {
|
||||
throw $this->getException($response->getErrorMessage());
|
||||
}
|
||||
|
||||
// create directory
|
||||
$dir = \dirname($path);
|
||||
if (!\file_exists($dir)) {
|
||||
if (!\mkdir($dir, 0777, true)) {
|
||||
throw new FilesystemException(\sprintf('Could not create the directory %s.', $dir));
|
||||
}
|
||||
}
|
||||
|
||||
// save
|
||||
if (\file_exists($path)) {
|
||||
if (!\is_writable($path)) {
|
||||
throw new FilesystemException(\sprintf('The file %s is not writable.', $path));
|
||||
}
|
||||
} else {
|
||||
if (!\touch($path)) {
|
||||
throw new FilesystemException(\sprintf('The file %s could not be created.', $path));
|
||||
}
|
||||
}
|
||||
|
||||
$file = \fopen($path, 'w');
|
||||
\stream_filter_append($file, 'convert.base64-decode');
|
||||
\fwrite($file, $response->getResultData('data'));
|
||||
\fclose($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return \Exception
|
||||
*/
|
||||
abstract protected function getException(string $message): \Exception;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\PageUtils;
|
||||
|
||||
use HeadlessChromium\Cookies\CookiesCollection;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class CookiesGetter extends ResponseWaiter
|
||||
{
|
||||
/**
|
||||
* Gets the cookies collection.
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
*
|
||||
* @return CookiesCollection
|
||||
*/
|
||||
public function getCookies()
|
||||
{
|
||||
return new CookiesCollection(
|
||||
$this->responseReader->getResponse()->getResultData('cookies')
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\PageUtils;
|
||||
|
||||
use HeadlessChromium\Communication\Response;
|
||||
use HeadlessChromium\Communication\ResponseReader;
|
||||
use HeadlessChromium\Exception\EvaluationFailed;
|
||||
use HeadlessChromium\Exception\JavascriptException;
|
||||
use HeadlessChromium\Page;
|
||||
|
||||
/**
|
||||
* Used to read data from page evaluation response.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PageEvaluation
|
||||
{
|
||||
/**
|
||||
* @var ResponseReader
|
||||
*/
|
||||
protected $responseReader;
|
||||
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $pageLoaderId;
|
||||
|
||||
/**
|
||||
* @var Page
|
||||
*/
|
||||
protected $page;
|
||||
|
||||
/**
|
||||
* PageEvaluation constructor.
|
||||
*
|
||||
* @param ResponseReader $responseReader\
|
||||
* @param string $pageLoaderId
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(ResponseReader $responseReader, $pageLoaderId, Page $page)
|
||||
{
|
||||
$this->responseReader = $responseReader;
|
||||
$this->pageLoaderId = $pageLoaderId;
|
||||
$this->page = $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the script requested a page reload this method will help to wait for the page to be fully reloaded.
|
||||
*/
|
||||
public function waitForPageReload($eventName = Page::LOAD, $timeout = 30000): void
|
||||
{
|
||||
$this->page->waitForReload($eventName, $timeout, $this->pageLoaderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the script to evaluate and to return a valid response.
|
||||
*
|
||||
* @param int|null $timeout
|
||||
*/
|
||||
public function waitForResponse(int $timeout = null)
|
||||
{
|
||||
$this->response = $this->responseReader->waitForResponse($timeout);
|
||||
|
||||
if (!$this->response->isSuccessful()) {
|
||||
throw new EvaluationFailed(\sprintf('Could not evaluate the script in the page. Message: "%s"', $this->response->getErrorMessage(true)));
|
||||
}
|
||||
|
||||
$result = $this->response->getResultData('result');
|
||||
|
||||
$resultSubType = $result['subtype'] ?? null;
|
||||
|
||||
if ('error' == $resultSubType) {
|
||||
// TODO dump javascript trace
|
||||
throw new JavascriptException('Error during javascript evaluation: '.$result['description']);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value produced when the script evaluated in the page.
|
||||
*
|
||||
* @param int|null $timeout
|
||||
*
|
||||
* @throws EvaluationFailed
|
||||
* @throws JavascriptException
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getReturnValue(int $timeout = null)
|
||||
{
|
||||
if (!$this->response) {
|
||||
$this->waitForResponse($timeout);
|
||||
}
|
||||
|
||||
return $this->response->getResultData('result')['value'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the return type of the response from the page.
|
||||
*
|
||||
* @param int|null $timeout
|
||||
*
|
||||
* @throws EvaluationFailed
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getReturnType(int $timeout = null)
|
||||
{
|
||||
if (!$this->response) {
|
||||
$this->waitForResponse($timeout);
|
||||
}
|
||||
|
||||
return $this->response->getResultData('result')['type'] ?? null;
|
||||
}
|
||||
}
|
128
pandora_console/vendor/chrome-php/chrome/src/PageUtils/PageLayoutMetrics.php
vendored
Normal file
128
pandora_console/vendor/chrome-php/chrome/src/PageUtils/PageLayoutMetrics.php
vendored
Normal file
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\PageUtils;
|
||||
|
||||
use HeadlessChromium\Exception\CommunicationException;
|
||||
|
||||
/**
|
||||
* Used to read layout metrics of the page.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PageLayoutMetrics extends ResponseWaiter
|
||||
{
|
||||
/**
|
||||
* Returns raw page metrics data.
|
||||
*
|
||||
* @throws CommunicationException\ResponseHasError
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\OperationTimedOut
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMetrics(): array
|
||||
{
|
||||
$response = $this->awaitResponse();
|
||||
|
||||
return $response->getData()['results'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns size of scrollable area.
|
||||
*
|
||||
* @throws CommunicationException\ResponseHasError
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\OperationTimedOut
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getContentSize(): array
|
||||
{
|
||||
return $this->getResultData('contentSize');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns metrics relating to the layout viewport.
|
||||
*
|
||||
* @throws CommunicationException\ResponseHasError
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\OperationTimedOut
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLayoutViewport(): array
|
||||
{
|
||||
return $this->getResultData('layoutViewport');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns metrics relating to the visual viewport.
|
||||
*
|
||||
* @throws CommunicationException\ResponseHasError
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\OperationTimedOut
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getVisualViewport()
|
||||
{
|
||||
return $this->getResultData('visualViewport');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns real size of scrollable area.
|
||||
*
|
||||
* @throws CommunicationException\ResponseHasError
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\OperationTimedOut
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCssContentSize(): array
|
||||
{
|
||||
return $this->getResultData('cssContentSize') ?? $this->getContentSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns real metrics relating to the layout viewport.
|
||||
*
|
||||
* @throws CommunicationException\ResponseHasError
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\OperationTimedOut
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCssLayoutViewport(): array
|
||||
{
|
||||
return $this->getResultData('cssLayoutViewport') ?? $this->getLayoutViewport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns real metrics relating to the visual viewport.
|
||||
*
|
||||
* @throws CommunicationException\ResponseHasError
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\OperationTimedOut
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCssVisualViewport()
|
||||
{
|
||||
return $this->getResultData('cssVisualViewport') ?? $this->getVisualViewport();
|
||||
}
|
||||
|
||||
/** @param 'layoutViewport'|'visualViewport'|'contentSize'|'cssLayoutViewport'|'cssVisualViewport'|'cssContentSize' $key */
|
||||
private function getResultData(string $key): array
|
||||
{
|
||||
return $this->awaitResponse()->getResultData($key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\PageUtils;
|
||||
|
||||
use HeadlessChromium\Communication\Message;
|
||||
use HeadlessChromium\Communication\ResponseReader;
|
||||
use HeadlessChromium\Exception;
|
||||
use HeadlessChromium\Exception\CommunicationException\ResponseHasError;
|
||||
use HeadlessChromium\Exception\NavigationExpired;
|
||||
use HeadlessChromium\Frame;
|
||||
use HeadlessChromium\Page;
|
||||
use HeadlessChromium\Utils;
|
||||
|
||||
/**
|
||||
* A class that is aimed to be used withing the method Page::navigate.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PageNavigation
|
||||
{
|
||||
/**
|
||||
* @var Frame
|
||||
*/
|
||||
protected $frame;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $previousLoaderId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $currentLoaderId;
|
||||
|
||||
/**
|
||||
* @var ResponseReader
|
||||
*/
|
||||
protected $navigateResponseReader;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @var Page
|
||||
*/
|
||||
protected $page;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $strict;
|
||||
|
||||
/**
|
||||
* PageNavigation constructor.
|
||||
*
|
||||
* @param Page $page
|
||||
* @param string $url
|
||||
* @param bool $strict by default this method will wait for the page to load even if a new navigation occurs
|
||||
* (ie: a new loader replaced the initial navigation). Passing $string to true will make the navigation to fail
|
||||
* if a new loader is generated
|
||||
*
|
||||
* @throws Exception\CommunicationException
|
||||
* @throws Exception\CommunicationException\CannotReadResponse
|
||||
* @throws Exception\CommunicationException\InvalidResponse
|
||||
*/
|
||||
public function __construct(Page $page, string $url, bool $strict = false)
|
||||
{
|
||||
// make sure latest loaderId was pulled
|
||||
$page->getSession()->getConnection()->readData();
|
||||
|
||||
// get previous loaderId for the navigation watcher
|
||||
$this->previousLoaderId = $page->getFrameManager()->getMainFrame()->getLatestLoaderId();
|
||||
|
||||
// send navigation message
|
||||
$this->navigateResponseReader = $page->getSession()->sendMessage(
|
||||
new Message('Page.navigate', ['url' => $url])
|
||||
);
|
||||
|
||||
$this->page = $page;
|
||||
$this->frame = $page->getFrameManager()->getMainFrame();
|
||||
$this->url = $url;
|
||||
$this->strict = $strict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the page loads.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```php
|
||||
* $navigation = $page->navigate('http://example.com');
|
||||
* try {
|
||||
* // wait max 30 seconds for dom content to load
|
||||
* $navigation->waitForNavigation(Page::DOM_CONTENT_LOADED, 30000);
|
||||
* } catch (OperationTimedOut $e) {
|
||||
* // too long to load
|
||||
* } catch (NavigationExpired $e) {
|
||||
* // an other page loaded since this navigation was initiated
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param string $eventName
|
||||
* @param int $timeout time in ms to wait for the navigation to complete. Default 30000 (30 seconds)
|
||||
*
|
||||
* @throws Exception\CommunicationException\CannotReadResponse
|
||||
* @throws Exception\CommunicationException\InvalidResponse
|
||||
* @throws Exception\NoResponseAvailable
|
||||
* @throws Exception\OperationTimedOut
|
||||
* @throws NavigationExpired
|
||||
* @throws ResponseHasError
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function waitForNavigation($eventName = Page::LOAD, int $timeout = null)
|
||||
{
|
||||
if (null === $timeout) {
|
||||
$timeout = 30000;
|
||||
}
|
||||
|
||||
return Utils::tryWithTimeout($timeout * 1000, $this->navigationComplete($eventName));
|
||||
}
|
||||
|
||||
/**
|
||||
* To be used with @see Utils::tryWithTimeout.
|
||||
*
|
||||
* @param string $eventName
|
||||
*
|
||||
* @throws Exception\CommunicationException\CannotReadResponse
|
||||
* @throws Exception\CommunicationException\InvalidResponse
|
||||
* @throws Exception\NoResponseAvailable
|
||||
* @throws NavigationExpired
|
||||
* @throws ResponseHasError
|
||||
*
|
||||
* @return bool|\Generator
|
||||
*/
|
||||
private function navigationComplete($eventName)
|
||||
{
|
||||
$delay = 500;
|
||||
|
||||
while (true) {
|
||||
// read the response only if it was not read already
|
||||
if (!$this->navigateResponseReader->hasResponse()) {
|
||||
$this->navigateResponseReader->checkForResponse();
|
||||
if ($this->navigateResponseReader->hasResponse()) {
|
||||
$response = $this->navigateResponseReader->getResponse();
|
||||
if (!$response->isSuccessful()) {
|
||||
throw new ResponseHasError(\sprintf('Cannot load page for url: "%s". Reason: %s', $this->url, $response->getErrorMessage()));
|
||||
}
|
||||
|
||||
$this->currentLoaderId = $response->getResultData('loaderId');
|
||||
} else {
|
||||
yield $delay;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure that the current loader is the good one
|
||||
if ($this->frame->getLatestLoaderId() === $this->currentLoaderId) {
|
||||
// check that lifecycle event exists
|
||||
if ($this->page->hasLifecycleEvent($eventName)) {
|
||||
return true;
|
||||
|
||||
// or else just wait for the new event to trigger
|
||||
} else {
|
||||
yield $delay;
|
||||
}
|
||||
|
||||
// else if frame has still the previous loader, wait for the new one
|
||||
} elseif ($this->frame->getLatestLoaderId() == $this->previousLoaderId) {
|
||||
yield $delay;
|
||||
|
||||
// else if a new loader is present that means that a new navigation started
|
||||
} else {
|
||||
// if strict then throw or else replace the old navigation with the new one
|
||||
if ($this->strict) {
|
||||
throw new NavigationExpired('The page has navigated to an other page and this navigation expired');
|
||||
} else {
|
||||
$this->currentLoaderId = $this->frame->getLatestLoaderId();
|
||||
}
|
||||
}
|
||||
|
||||
$this->page->getSession()->getConnection()->readData();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\PageUtils;
|
||||
|
||||
use HeadlessChromium\Communication\Message;
|
||||
use HeadlessChromium\Exception\PdfFailed;
|
||||
use HeadlessChromium\Page;
|
||||
|
||||
class PagePdf extends AbstractBinaryInput
|
||||
{
|
||||
private const TYPE_NUMERIC = 1;
|
||||
private const TYPE_STRING = 2;
|
||||
private const TYPE_BOOLEAN = 3;
|
||||
|
||||
/**
|
||||
* @see https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
|
||||
*/
|
||||
private const OPTIONS = [
|
||||
'landscape' => self::TYPE_BOOLEAN,
|
||||
'printBackground' => self::TYPE_BOOLEAN,
|
||||
'displayHeaderFooter' => self::TYPE_BOOLEAN,
|
||||
'headerTemplate' => self::TYPE_STRING,
|
||||
'footerTemplate' => self::TYPE_STRING,
|
||||
'paperWidth' => self::TYPE_NUMERIC,
|
||||
'paperHeight' => self::TYPE_NUMERIC,
|
||||
'marginTop' => self::TYPE_NUMERIC,
|
||||
'marginBottom' => self::TYPE_NUMERIC,
|
||||
'marginLeft' => self::TYPE_NUMERIC,
|
||||
'marginRight' => self::TYPE_NUMERIC,
|
||||
'pageRanges' => self::TYPE_STRING,
|
||||
'ignoreInvalidPageRanges' => self::TYPE_BOOLEAN,
|
||||
'preferCSSPageSize' => self::TYPE_BOOLEAN,
|
||||
'scale' => self::TYPE_NUMERIC,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Page
|
||||
*/
|
||||
private $page;
|
||||
|
||||
private $options = [];
|
||||
|
||||
/**
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
*/
|
||||
public function __construct(Page $page, array $options = [])
|
||||
{
|
||||
$this->page = $page;
|
||||
|
||||
$this->setOptions($options)->print();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException
|
||||
*/
|
||||
public function print(): self
|
||||
{
|
||||
$responseReader = $this->page->getSession()->sendMessage(new Message('Page.printToPDF', $this->options));
|
||||
|
||||
parent::__construct($responseReader);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setOptions(array $options): self
|
||||
{
|
||||
\array_map([$this, 'validateOption'], \array_keys($options), $options);
|
||||
|
||||
$this->options = $options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string|int|float|bool $value
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function validateOption(string $name, $value): bool
|
||||
{
|
||||
if (false === \in_array($name, \array_keys(self::OPTIONS))) {
|
||||
throw new \InvalidArgumentException("Unknown option '{$name}' for print to pdf.");
|
||||
}
|
||||
switch (self::OPTIONS[$name]) {
|
||||
case self::TYPE_NUMERIC:
|
||||
\is_numeric($value) || $this->invalidArgument("Invalid option '{$name}' for print to pdf. Must be numeric.");
|
||||
break;
|
||||
case self::TYPE_STRING:
|
||||
\is_string($value) || $this->invalidArgument("Invalid option '{$name}' for print to pdf. Must be string.");
|
||||
break;
|
||||
case self::TYPE_BOOLEAN:
|
||||
\is_bool($value) || $this->invalidArgument("Invalid option '{$name}' for print to pdf. Must be boolean.");
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function getException(string $message): \Exception
|
||||
{
|
||||
return new PdfFailed(
|
||||
\sprintf('Cannot make a PDF. Reason : %s', $message)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to throw exception in expression when running in php 7.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function invalidArgument(string $message): void
|
||||
{
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\PageUtils;
|
||||
|
||||
use HeadlessChromium\Exception\ScreenshotFailed;
|
||||
|
||||
class PageScreenshot extends AbstractBinaryInput
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function getException(string $message): \Exception
|
||||
{
|
||||
return new ScreenshotFailed(
|
||||
\sprintf('Cannot make a screenshot. Reason : %s', $message)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium\PageUtils;
|
||||
|
||||
use HeadlessChromium\Communication\Response;
|
||||
use HeadlessChromium\Communication\ResponseReader;
|
||||
use HeadlessChromium\Exception\CommunicationException\ResponseHasError;
|
||||
|
||||
class ResponseWaiter
|
||||
{
|
||||
/**
|
||||
* @var ResponseReader
|
||||
*/
|
||||
protected $responseReader;
|
||||
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* @param ResponseReader $responseReader
|
||||
*/
|
||||
public function __construct(ResponseReader $responseReader)
|
||||
{
|
||||
$this->responseReader = $responseReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chainable wait for response.
|
||||
*
|
||||
* @param int $time
|
||||
*
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\OperationTimedOut
|
||||
* @throws \HeadlessChromium\Exception\CommunicationException\ResponseHasError
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function await(int $time = null)
|
||||
{
|
||||
$this->response = $this->responseReader->waitForResponse($time);
|
||||
|
||||
if (!$this->response->isSuccessful()) {
|
||||
throw new ResponseHasError($this->response->getErrorMessage(true));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for response and return it.
|
||||
*
|
||||
* @param int|null $time
|
||||
*
|
||||
* @throws ResponseHasError
|
||||
* @throws \HeadlessChromium\Exception\NoResponseAvailable
|
||||
* @throws \HeadlessChromium\Exception\OperationTimedOut
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function awaitResponse(int $time = null): Response
|
||||
{
|
||||
if (!$this->response) {
|
||||
$this->await($time);
|
||||
}
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseReader
|
||||
*/
|
||||
public function getResponseReader(): ResponseReader
|
||||
{
|
||||
return $this->responseReader;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chrome PHP.
|
||||
*
|
||||
* (c) Soufiane Ghzal <sghzal@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HeadlessChromium;
|
||||
|
||||
use HeadlessChromium\Communication\Connection;
|
||||
use HeadlessChromium\Communication\Message;
|
||||
use HeadlessChromium\Dom\Selector\Selector;
|
||||
use HeadlessChromium\Exception\CommunicationException;
|
||||
use HeadlessChromium\Exception\JavascriptException;
|
||||
use HeadlessChromium\Exception\OperationTimedOut;
|
||||
|
||||
class Utils
|
||||
{
|
||||
/**
|
||||
* Iterates on the given generator until the generator returns a value or the given timeout is reached.
|
||||
*
|
||||
* When the generator yields a value, this value is the time in microseconds to wait before trying again.
|
||||
*
|
||||
* Example waiting for a process to complete:
|
||||
*
|
||||
* ```php
|
||||
* // wait for process to close
|
||||
* $generator = function (Process $process) {
|
||||
* while ($process->isRunning()) {
|
||||
* yield 2 * 1000; // wait for 2ms
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* $timeout = 8 * 1000 * 1000; // 8 seconds
|
||||
*
|
||||
* try {
|
||||
* Utils::tryWithTimeout($timeout, $generator($this->process));
|
||||
* } catch (OperationTimedOut $e) {
|
||||
* // log
|
||||
* $this->logger->debug('process: process didn\'t close by itself');
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param int $timeoutMicroSec
|
||||
* @param \Generator $generator
|
||||
* @param callable|null $onTimeout
|
||||
*
|
||||
* @throws OperationTimedOut
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function tryWithTimeout(int $timeoutMicroSec, \Generator $generator, callable $onTimeout = null)
|
||||
{
|
||||
$waitUntilMicroSec = \hrtime(true) / 1000 + $timeoutMicroSec;
|
||||
|
||||
foreach ($generator as $v) {
|
||||
// if timeout reached or if time+delay exceed timeout stop the execution
|
||||
if (\hrtime(true) / 1000 + (int) $v >= $waitUntilMicroSec) {
|
||||
if (null !== $onTimeout) {
|
||||
// if callback was set execute it
|
||||
return $onTimeout();
|
||||
}
|
||||
throw OperationTimedOut::createFromTimeout($timeoutMicroSec);
|
||||
}
|
||||
|
||||
\usleep((int) $v);
|
||||
}
|
||||
|
||||
return $generator->getReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all pages for the given connection.
|
||||
*
|
||||
* @param Connection $connection
|
||||
*/
|
||||
public static function closeAllPage(Connection $connection): void
|
||||
{
|
||||
// get targets
|
||||
$targetsResponse = $connection->sendMessageSync(new Message('Target.getTargets'));
|
||||
|
||||
if ($targetsResponse->isSuccessful()) {
|
||||
foreach ($targetsResponse['result']['targetInfos'] as $target) {
|
||||
if ('page' === $target['type']) {
|
||||
$connection->sendMessageSync(
|
||||
new Message('Target.closeTarget', ['targetId' => $target['targetId']])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CommunicationException
|
||||
* @throws Exception\EvaluationFailed
|
||||
* @throws JavascriptException
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getElementPositionFromPage(Page $page, Selector $selector, int $position = 1)
|
||||
{
|
||||
$elementCount = $page
|
||||
->evaluate(\sprintf('JSON.parse(JSON.stringify(%s));', $selector->expressionCount()))
|
||||
->getReturnValue();
|
||||
|
||||
$position = \max(1, $position);
|
||||
$position = \min($position, $elementCount);
|
||||
|
||||
return $page
|
||||
->evaluate(\sprintf('JSON.parse(JSON.stringify(%s.getBoundingClientRect()));', $selector->expressionFindOne($position)))
|
||||
->getReturnValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2010-2011 Chris Boden <cboden@gmail.com>
|
||||
Copyright (c) 2010-2011 Nico Kaiser <kaiser@boerse-go.de>
|
||||
Copyright (c) 2011-2012 Simon Samtleben <web@lemmingzshadow.net>
|
||||
Copyright (c) 2012-2017 Dominic Scheirlinck <dominic@varspool.com>
|
||||
Copyright (c) 2017-2020 Zane Chua <zane.jocelyn@gmail.com>
|
||||
Copyright (c) 2021-2022 Graham Campbell <hello@gjcampbell.co.uk>
|
||||
|
||||
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.
|
|
@ -0,0 +1,7 @@
|
|||
install:
|
||||
composer update
|
||||
composer bin all update
|
||||
|
||||
test:
|
||||
vendor/bin/phpunit
|
||||
vendor/bin/phpstan analyze
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "chrome-php/wrench",
|
||||
"description": "A simple PHP WebSocket implementation",
|
||||
"keywords": ["websocket", "websockets", "hybi"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.3 || ^8.0",
|
||||
"ext-sockets": "*",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0",
|
||||
"symfony/polyfill-php80": "^1.26"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
||||
"phpunit/phpunit": "^9.5.23"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Wrench\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Wrench\\": "tests/"
|
||||
}
|
||||
},
|
||||
"conflict": {
|
||||
"wrench/wrench": "*"
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist",
|
||||
"allow-plugins": {
|
||||
"bamarni/composer-bin-plugin": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
}
|
||||
}
|
13
pandora_console/vendor/chrome-php/wrench/src/Application/BinaryDataHandlerInterface.php
vendored
Normal file
13
pandora_console/vendor/chrome-php/wrench/src/Application/BinaryDataHandlerInterface.php
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Application;
|
||||
|
||||
use Wrench\Connection;
|
||||
|
||||
interface BinaryDataHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Handle binary data received from a client.
|
||||
*/
|
||||
public function onBinaryData(string $binaryData, Connection $connection): void;
|
||||
}
|
12
pandora_console/vendor/chrome-php/wrench/src/Application/ConnectionHandlerInterface.php
vendored
Normal file
12
pandora_console/vendor/chrome-php/wrench/src/Application/ConnectionHandlerInterface.php
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Application;
|
||||
|
||||
use Wrench\Connection;
|
||||
|
||||
interface ConnectionHandlerInterface
|
||||
{
|
||||
public function onConnect(Connection $connection): void;
|
||||
|
||||
public function onDisconnect(Connection $connection): void;
|
||||
}
|
13
pandora_console/vendor/chrome-php/wrench/src/Application/DataHandlerInterface.php
vendored
Normal file
13
pandora_console/vendor/chrome-php/wrench/src/Application/DataHandlerInterface.php
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Application;
|
||||
|
||||
use Wrench\Connection;
|
||||
|
||||
interface DataHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Handle data received from a client.
|
||||
*/
|
||||
public function onData(string $data, Connection $connection): void;
|
||||
}
|
11
pandora_console/vendor/chrome-php/wrench/src/Application/UpdateHandlerInterface.php
vendored
Normal file
11
pandora_console/vendor/chrome-php/wrench/src/Application/UpdateHandlerInterface.php
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Application;
|
||||
|
||||
interface UpdateHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Handle an update tick.
|
||||
*/
|
||||
public function onUpdate(): void;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench;
|
||||
|
||||
use Wrench\Listener\OriginPolicy;
|
||||
use Wrench\Listener\RateLimiter;
|
||||
|
||||
class BasicServer extends Server
|
||||
{
|
||||
protected $rateLimiter;
|
||||
protected $originPolicy;
|
||||
|
||||
public function __construct(string $uri, array $options = [])
|
||||
{
|
||||
parent::__construct($uri, $options);
|
||||
|
||||
$this->configureRateLimiter();
|
||||
$this->configureOriginPolicy();
|
||||
}
|
||||
|
||||
protected function configureRateLimiter(): void
|
||||
{
|
||||
$class = $this->options['rate_limiter_class'];
|
||||
$this->rateLimiter = new $class($this->options['rate_limiter_options']);
|
||||
$this->rateLimiter->listen($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the origin policy.
|
||||
*/
|
||||
protected function configureOriginPolicy(): void
|
||||
{
|
||||
$class = $this->options['origin_policy_class'];
|
||||
$this->originPolicy = new $class($this->options['allowed_origins']);
|
||||
|
||||
if ($this->options['check_origin']) {
|
||||
$this->originPolicy->listen($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Wrench.Server::configure()
|
||||
*/
|
||||
protected function configure(array $options): void
|
||||
{
|
||||
$options = \array_merge([
|
||||
'check_origin' => true,
|
||||
'allowed_origins' => [],
|
||||
'origin_policy_class' => OriginPolicy::class,
|
||||
'rate_limiter_class' => RateLimiter::class,
|
||||
'rate_limiter_options' => [
|
||||
'connections' => 200, // Total
|
||||
'connections_per_ip' => 5, // At once
|
||||
'requests_per_minute' => 200, // Per connection
|
||||
],
|
||||
], $options);
|
||||
|
||||
parent::configure($options);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Wrench\Exception\FrameException;
|
||||
use Wrench\Exception\HandshakeException;
|
||||
use Wrench\Exception\SocketException;
|
||||
use Wrench\Payload\Payload;
|
||||
use Wrench\Payload\PayloadHandler;
|
||||
use Wrench\Protocol\Protocol;
|
||||
use Wrench\Socket\ClientSocket;
|
||||
use Wrench\Util\Configurable;
|
||||
|
||||
/**
|
||||
* Client class.
|
||||
*
|
||||
* Represents a websocket client
|
||||
*/
|
||||
class Client extends Configurable
|
||||
{
|
||||
/**
|
||||
* @var int bytes
|
||||
*/
|
||||
public const MAX_HANDSHAKE_RESPONSE = 1500;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $uri;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $origin;
|
||||
|
||||
/**
|
||||
* @var ClientSocket|null
|
||||
*/
|
||||
protected $socket;
|
||||
|
||||
/**
|
||||
* Request headers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $headers = [];
|
||||
|
||||
/**
|
||||
* Whether the client is connected.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $connected = false;
|
||||
|
||||
/**
|
||||
* @var PayloadHandler|null
|
||||
*/
|
||||
protected $payloadHandler = null;
|
||||
|
||||
/**
|
||||
* Complete received payloads.
|
||||
*
|
||||
* @var array<Payload>
|
||||
*/
|
||||
protected $received = [];
|
||||
|
||||
/**
|
||||
* @param string $origin The origin to include in the handshake (required
|
||||
* in later versions of the protocol)
|
||||
* @param array $options (optional) Array of options
|
||||
* - socket => AbstractSocket instance (otherwise created)
|
||||
* - protocol => Protocol
|
||||
*/
|
||||
public function __construct(string $uri, string $origin, array $options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
if (!$uri) {
|
||||
throw new InvalidArgumentException('No URI specified');
|
||||
}
|
||||
$this->uri = $uri;
|
||||
|
||||
if (!$origin) {
|
||||
throw new InvalidArgumentException('No origin specified');
|
||||
}
|
||||
$this->origin = $origin;
|
||||
|
||||
$this->protocol->validateUri($this->uri);
|
||||
$this->protocol->validateOriginUri($this->origin);
|
||||
|
||||
$this->configureSocket();
|
||||
$this->configurePayloadHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the client socket.
|
||||
*/
|
||||
protected function configureSocket(): void
|
||||
{
|
||||
$class = $this->options['socket_class'];
|
||||
$options = $this->options['socket_options'];
|
||||
$this->socket = new $class($this->uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the payload handler.
|
||||
*/
|
||||
protected function configurePayloadHandler(): void
|
||||
{
|
||||
$this->payloadHandler = new PayloadHandler([$this, 'onData'], $this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload receiver
|
||||
* Public because called from our PayloadHandler. Don't call us, we'll call
|
||||
* you (via the on_data_callback option).
|
||||
*
|
||||
* @param Payload $payload
|
||||
*/
|
||||
public function onData(Payload $payload): void
|
||||
{
|
||||
$this->received[] = $payload;
|
||||
if (($callback = $this->options['on_data_callback'])) {
|
||||
\call_user_func($callback, $payload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a request header to be included in the initial handshake.
|
||||
*
|
||||
* For example, to include a Cookie header.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addRequestHeader(string $name, string $value): void
|
||||
{
|
||||
$this->headers[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data to the socket.
|
||||
*
|
||||
* @param int $type See Protocol::TYPE_*
|
||||
*
|
||||
* @return bool Success
|
||||
*/
|
||||
public function sendData(string $data, int $type = Protocol::TYPE_TEXT, bool $masked = true): bool
|
||||
{
|
||||
if (!$this->isConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$payload = $this->protocol->getPayload();
|
||||
|
||||
$payload->encode(
|
||||
$data,
|
||||
$type,
|
||||
$masked
|
||||
);
|
||||
|
||||
return $payload->sendToSocket($this->socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the client is currently connected
|
||||
* Also checks the state of the underlying socket.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
if (false === $this->connected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the socket is still connected
|
||||
if (false === $this->socket->isConnected()) {
|
||||
$this->connected = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives data sent by the server.
|
||||
*
|
||||
* @return array<Payload> Payload received since the last call to receive()
|
||||
*/
|
||||
public function receive(): ?array
|
||||
{
|
||||
if (!$this->isConnected()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $this->socket->receive();
|
||||
|
||||
if (!$data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->payloadHandler->handle($data);
|
||||
$received = $this->received;
|
||||
$this->received = [];
|
||||
|
||||
return $received;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the server.
|
||||
*
|
||||
* @throws HandshakeException
|
||||
* @throws SocketException
|
||||
*
|
||||
* @return bool Whether a new connection was made
|
||||
*/
|
||||
public function connect(): bool
|
||||
{
|
||||
if ($this->isConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->socket->connect();
|
||||
} catch (\Exception $ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$key = $this->protocol->generateKey();
|
||||
$handshake = $this->protocol->getRequestHandshake(
|
||||
$this->uri,
|
||||
$key,
|
||||
$this->origin,
|
||||
$this->headers
|
||||
);
|
||||
|
||||
$this->socket->send($handshake);
|
||||
$response = $this->socket->receive(self::MAX_HANDSHAKE_RESPONSE);
|
||||
|
||||
return $this->connected =
|
||||
$this->protocol->validateResponseHandshake($response, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the underlying socket, and marks the client as disconnected.
|
||||
*
|
||||
* @param int $reason Reason for disconnecting. See Protocol::CLOSE_
|
||||
*
|
||||
* @throws SocketException
|
||||
* @throws FrameException
|
||||
*/
|
||||
public function disconnect(int $reason = Protocol::CLOSE_NORMAL): bool
|
||||
{
|
||||
if (false === $this->connected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$payload = $this->protocol->getClosePayload($reason);
|
||||
|
||||
if ($this->socket) {
|
||||
if (!$payload->sendToSocket($this->socket)) {
|
||||
throw new FrameException('Unexpected exception when sending Close frame.');
|
||||
}
|
||||
// The client SHOULD wait for the server to close the connection
|
||||
$this->socket->receive();
|
||||
$this->socket->disconnect();
|
||||
}
|
||||
|
||||
$this->connected = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure options.
|
||||
*/
|
||||
protected function configure(array $options): void
|
||||
{
|
||||
$options = \array_merge([
|
||||
'socket_class' => ClientSocket::class,
|
||||
'on_data_callback' => null,
|
||||
'socket_options' => [],
|
||||
], $options);
|
||||
|
||||
parent::configure($options);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,489 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench;
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
use Wrench\Application\BinaryDataHandlerInterface;
|
||||
use Wrench\Application\ConnectionHandlerInterface;
|
||||
use Wrench\Application\DataHandlerInterface;
|
||||
use Wrench\Application\UpdateHandlerInterface;
|
||||
use Wrench\Exception\BadRequestException;
|
||||
use Wrench\Exception\CloseException;
|
||||
use Wrench\Exception\ConnectionException;
|
||||
use Wrench\Exception\Exception as WrenchException;
|
||||
use Wrench\Exception\HandshakeException;
|
||||
use Wrench\Payload\Payload;
|
||||
use Wrench\Payload\PayloadHandler;
|
||||
use Wrench\Protocol\Protocol;
|
||||
use Wrench\Socket\ServerClientSocket;
|
||||
use Wrench\Util\Configurable;
|
||||
|
||||
/**
|
||||
* Represents a client connection on the server side.
|
||||
*
|
||||
* i.e. the `Server` manages a bunch of `Connection`s
|
||||
*/
|
||||
class Connection extends Configurable implements LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* @var ConnectionManager
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
/**
|
||||
* Wraps the client connection resource.
|
||||
*
|
||||
* @var ServerClientSocket
|
||||
*/
|
||||
protected $socket;
|
||||
|
||||
/**
|
||||
* Whether the connection has successfully handshaken.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $handshaked = false;
|
||||
|
||||
/**
|
||||
* The application this connection belongs to.
|
||||
*
|
||||
* @var DataHandlerInterface|ConnectionHandlerInterface|UpdateHandlerInterface|null
|
||||
*/
|
||||
protected $application = null;
|
||||
|
||||
/**
|
||||
* The IP address of the client.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $ip;
|
||||
|
||||
/**
|
||||
* The port of the client.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $port;
|
||||
|
||||
/**
|
||||
* The array of headers included with the original request (like Cookie for example)
|
||||
* The headers specific to the web sockets handshaking have been stripped out.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $headers = null;
|
||||
|
||||
/**
|
||||
* The array of query parameters included in the original request
|
||||
* The array is in the format 'key' => 'value'.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $queryParams = null;
|
||||
|
||||
/**
|
||||
* Connection ID.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $id = null;
|
||||
|
||||
/**
|
||||
* @var PayloadHandler
|
||||
*/
|
||||
protected $payloadHandler;
|
||||
|
||||
public function __construct(
|
||||
ConnectionManager $manager,
|
||||
ServerClientSocket $socket,
|
||||
array $options = []
|
||||
) {
|
||||
$this->manager = $manager;
|
||||
$this->socket = $socket;
|
||||
$this->logger = new NullLogger();
|
||||
|
||||
parent::__construct($options);
|
||||
|
||||
$this->configureClientInformation();
|
||||
$this->configurePayloadHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function configureClientInformation(): void
|
||||
{
|
||||
$this->ip = $this->socket->getIp();
|
||||
$this->port = $this->socket->getPort();
|
||||
$this->generateClientId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the client ID.
|
||||
*
|
||||
* We hash the client ID to prevent leakage of information if another client
|
||||
* happens to get a hold of an ID. The secret *must* be lengthy, and must
|
||||
* be kept secret for this to work: otherwise it's trivial to search the space
|
||||
* of possible IP addresses/ports (well, if not trivial, at least very fast).
|
||||
*/
|
||||
protected function generateClientId(): void
|
||||
{
|
||||
$this->id = \bin2hex(\random_bytes(32));
|
||||
}
|
||||
|
||||
protected function configurePayloadHandler(): void
|
||||
{
|
||||
$this->payloadHandler = new PayloadHandler(
|
||||
[$this, 'handlePayload'],
|
||||
$this->options
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connection manager of this connection.
|
||||
*
|
||||
* @return \Wrench\ConnectionManager
|
||||
*/
|
||||
public function getConnectionManager(): ConnectionManager
|
||||
{
|
||||
return $this->manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a complete payload received from the client.
|
||||
*
|
||||
* Public because called from our PayloadHandler
|
||||
*
|
||||
* @param Payload $payload
|
||||
*
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function handlePayload(Payload $payload): void
|
||||
{
|
||||
$app = $this->getClientApplication();
|
||||
|
||||
$this->logger->debug('Handling payload: '.$payload->getPayload());
|
||||
|
||||
switch ($type = $payload->getType()) {
|
||||
case Protocol::TYPE_TEXT:
|
||||
if ($app instanceof DataHandlerInterface) {
|
||||
$app->onData((string) $payload, $this);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
case Protocol::TYPE_BINARY:
|
||||
if ($app instanceof BinaryDataHandlerInterface) {
|
||||
$app->onBinaryData((string) $payload, $this);
|
||||
} else {
|
||||
$this->close(1003);
|
||||
}
|
||||
break;
|
||||
|
||||
case Protocol::TYPE_PING:
|
||||
$this->logger->notice('Ping received');
|
||||
$this->send($payload->getPayload(), Protocol::TYPE_PONG);
|
||||
$this->logger->debug('Pong!');
|
||||
break;
|
||||
|
||||
/**
|
||||
* A Pong frame MAY be sent unsolicited. This serves as a
|
||||
* unidirectional heartbeat. A response to an unsolicited Pong
|
||||
* frame is not expected.
|
||||
*/
|
||||
case Protocol::TYPE_PONG:
|
||||
$this->logger->info('Received unsolicited pong');
|
||||
break;
|
||||
|
||||
case Protocol::TYPE_CLOSE:
|
||||
$this->logger->notice('Close frame received');
|
||||
$this->close();
|
||||
$this->logger->debug('Disconnected');
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ConnectionException('Unhandled payload type');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the client application.
|
||||
*
|
||||
* @return BinaryDataHandlerInterface|ConnectionHandlerInterface|DataHandlerInterface|UpdateHandlerInterface|false
|
||||
*/
|
||||
public function getClientApplication()
|
||||
{
|
||||
return $this->application ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection according to the WebSocket protocol.
|
||||
*
|
||||
* If an endpoint receives a Close frame and that endpoint did not
|
||||
* previously send a Close frame, the endpoint MUST send a Close frame
|
||||
* in response. It SHOULD do so as soon as is practical. An endpoint
|
||||
* MAY delay sending a close frame until its current message is sent
|
||||
* (for instance, if the majority of a fragmented message is already
|
||||
* sent, an endpoint MAY send the remaining fragments before sending a
|
||||
* Close frame). However, there is no guarantee that the endpoint which
|
||||
* has already sent a Close frame will continue to process data.
|
||||
* After both sending and receiving a close message, an endpoint
|
||||
* considers the WebSocket connection closed, and MUST close the
|
||||
* underlying TCP connection. The server MUST close the underlying TCP
|
||||
* connection immediately; the client SHOULD wait for the server to
|
||||
* close the connection but MAY close the connection at any time after
|
||||
* sending and receiving a close message, e.g. if it has not received a
|
||||
* TCP close from the server in a reasonable time period.
|
||||
*
|
||||
* @param int $code
|
||||
* @param string $reason The human readable reason the connection was closed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function close(int $code = Protocol::CLOSE_NORMAL, string $reason = null): bool
|
||||
{
|
||||
try {
|
||||
if (!$this->handshaked) {
|
||||
$response = $this->protocol->getResponseError($code);
|
||||
$this->socket->send($response);
|
||||
} else {
|
||||
$response = $this->protocol->getClosePayload($code, false);
|
||||
$response->sendToSocket($this->socket);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->warning('Unable to send close message');
|
||||
}
|
||||
|
||||
if ($this->application instanceof ConnectionHandlerInterface) {
|
||||
$this->application->onDisconnect($this);
|
||||
}
|
||||
|
||||
$this->socket->disconnect();
|
||||
$this->manager->removeConnection($this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the payload to the connection.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param int $type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function send($data, int $type = Protocol::TYPE_TEXT): bool
|
||||
{
|
||||
if (!$this->handshaked) {
|
||||
throw new HandshakeException('Connection is not handshaked');
|
||||
}
|
||||
|
||||
$payload = $this->protocol->getPayload();
|
||||
if (!\is_scalar($data) && !$data instanceof Payload) {
|
||||
$data = \json_encode($data);
|
||||
}
|
||||
|
||||
// Servers don't send masked payloads
|
||||
$payload->encode($data, $type, false);
|
||||
|
||||
if (!$payload->sendToSocket($this->socket)) {
|
||||
$this->logger->warning('Could not send payload to client');
|
||||
throw new ConnectionException('Could not send data to connection: '.$this->socket->getLastError());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes data on the socket.
|
||||
*
|
||||
* @throws CloseException
|
||||
*/
|
||||
public function process(): void
|
||||
{
|
||||
$data = $this->socket->receive();
|
||||
|
||||
if ('' === $data) {
|
||||
throw new CloseException('Error reading data from socket: '.$this->socket->getLastError());
|
||||
}
|
||||
|
||||
$this->onData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data receiver.
|
||||
*
|
||||
* Called by the connection manager when the connection has received data
|
||||
*
|
||||
* @param string $data
|
||||
*/
|
||||
public function onData($data): void
|
||||
{
|
||||
if ($this->handshaked) {
|
||||
$this->handle($data);
|
||||
} else {
|
||||
$this->handshake($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a websocket handshake.
|
||||
*
|
||||
* @throws BadRequestException
|
||||
* @throws HandshakeException
|
||||
* @throws WrenchException
|
||||
*/
|
||||
public function handshake(string $data): void
|
||||
{
|
||||
try {
|
||||
[$path, $origin, $key, $extensions, $protocol, $headers, $params]
|
||||
= $this->protocol->validateRequestHandshake($data);
|
||||
|
||||
$this->headers = $headers;
|
||||
$this->queryParams = $params;
|
||||
|
||||
$this->application = $this->manager->getApplicationForPath($path);
|
||||
if (!$this->application) {
|
||||
throw new BadRequestException('Invalid application');
|
||||
}
|
||||
|
||||
$this->manager->getServer()->notify(
|
||||
Server::EVENT_HANDSHAKE_REQUEST,
|
||||
[$this, $path, $origin, $key, $extensions]
|
||||
);
|
||||
|
||||
$response = $this->protocol->getResponseHandshake($key);
|
||||
|
||||
if (!$this->socket->isConnected()) {
|
||||
throw new HandshakeException('Socket is not connected');
|
||||
}
|
||||
|
||||
if (null === $this->socket->send($response)) {
|
||||
throw new HandshakeException('Could not send handshake response');
|
||||
}
|
||||
|
||||
$this->handshaked = true;
|
||||
|
||||
$this->logger->info(\sprintf(
|
||||
'Handshake successful: %s:%d (%s) connected to %s',
|
||||
$this->getIp(),
|
||||
$this->getPort(),
|
||||
$this->getId(),
|
||||
$path
|
||||
));
|
||||
|
||||
$this->manager->getServer()->notify(
|
||||
Server::EVENT_HANDSHAKE_SUCCESSFUL,
|
||||
[$this]
|
||||
);
|
||||
|
||||
if ($this->application instanceof ConnectionHandlerInterface) {
|
||||
$this->application->onConnect($this);
|
||||
}
|
||||
} catch (WrenchException $e) {
|
||||
$this->logger->error('Handshake failed: {exception}', [
|
||||
'exception' => $e,
|
||||
]);
|
||||
$this->close(Protocol::CLOSE_PROTOCOL_ERROR, (string) $e);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the IP address of the connection.
|
||||
*
|
||||
* @return string Usually dotted quad notation
|
||||
*/
|
||||
public function getIp(): string
|
||||
{
|
||||
return $this->ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port of the connection.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPort(): int
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connection ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle data received from the client.
|
||||
*
|
||||
* The data passed in may belong to several different frames across one or
|
||||
* more protocols. It may not even contain a single complete frame. This method
|
||||
* manages slotting the data into separate payload objects.
|
||||
*
|
||||
* @todo An endpoint MUST be capable of handling control frames in the
|
||||
* middle of a fragmented message.
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle($data): void
|
||||
{
|
||||
$this->payloadHandler->handle($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the non-web-sockets headers included with the original request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the query parameters included with the original request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getQueryParams(): array
|
||||
{
|
||||
return $this->queryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the socket object.
|
||||
*
|
||||
* @return Socket\ServerClientSocket
|
||||
*/
|
||||
public function getSocket(): ServerClientSocket
|
||||
{
|
||||
return $this->socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Wrench\Util.Configurable::configure()
|
||||
*/
|
||||
protected function configure(array $options): void
|
||||
{
|
||||
$options = \array_merge([
|
||||
'connection_id_secret' => 'asu5gj656h64Da(0crt8pud%^WAYWW$u76dwb',
|
||||
'connection_id_algo' => 'sha512',
|
||||
], $options);
|
||||
|
||||
parent::configure($options);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,323 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench;
|
||||
|
||||
use Countable;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Wrench\Application\BinaryDataHandlerInterface;
|
||||
use Wrench\Application\ConnectionHandlerInterface;
|
||||
use Wrench\Application\DataHandlerInterface;
|
||||
use Wrench\Application\UpdateHandlerInterface;
|
||||
use Wrench\Exception\CloseException;
|
||||
use Wrench\Exception\ConnectionException;
|
||||
use Wrench\Exception\Exception as WrenchException;
|
||||
use Wrench\Protocol\Protocol;
|
||||
use Wrench\Socket\ServerClientSocket;
|
||||
use Wrench\Socket\ServerSocket;
|
||||
use Wrench\Util\Configurable;
|
||||
|
||||
class ConnectionManager extends Configurable implements Countable, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
public const TIMEOUT_SELECT = 0;
|
||||
public const TIMEOUT_SELECT_MICROSEC = 200000;
|
||||
|
||||
/**
|
||||
* @var Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Master socket.
|
||||
*
|
||||
* @var ServerSocket
|
||||
*/
|
||||
protected $socket;
|
||||
|
||||
/**
|
||||
* An array of client connections.
|
||||
*
|
||||
* @var array<int, Connection>
|
||||
*/
|
||||
protected $connections = [];
|
||||
|
||||
/**
|
||||
* An array of raw socket resources, corresponding to connections, roughly.
|
||||
*
|
||||
* @var array<int, resource>
|
||||
*/
|
||||
protected $resources = [];
|
||||
|
||||
public function __construct(Server $server, array $options = [])
|
||||
{
|
||||
$this->logger = new NullLogger();
|
||||
$this->server = $server;
|
||||
|
||||
parent::__construct($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Countable::count()
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->connections);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application associated with the given path.
|
||||
*
|
||||
* @return BinaryDataHandlerInterface|ConnectionHandlerInterface|DataHandlerInterface|UpdateHandlerInterface|null
|
||||
*/
|
||||
public function getApplicationForPath(string $path): ?object
|
||||
{
|
||||
$path = \ltrim($path, '/');
|
||||
|
||||
return $this->server->getApplication($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens on the main socket.
|
||||
*
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function listen(): void
|
||||
{
|
||||
$this->socket->listen();
|
||||
$this->resources[$this->socket->getResourceId()] = $this->socket->getResource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Select and process an array of resources.
|
||||
*/
|
||||
public function selectAndProcess(): void
|
||||
{
|
||||
$read = $this->resources;
|
||||
$unused_write = null;
|
||||
$unused_exception = null;
|
||||
|
||||
\stream_select(
|
||||
$read,
|
||||
$unused_write,
|
||||
$unused_exception,
|
||||
$this->options['timeout_select'],
|
||||
$this->options['timeout_select_microsec']
|
||||
);
|
||||
|
||||
foreach ($read as $socket) {
|
||||
if ($socket == $this->socket->getResource()) {
|
||||
$this->processMasterSocket();
|
||||
} else {
|
||||
$this->processClientSocket($socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process events on the master socket ($this->socket).
|
||||
*/
|
||||
protected function processMasterSocket(): void
|
||||
{
|
||||
$new = null;
|
||||
|
||||
try {
|
||||
$new = $this->socket->accept();
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Socket error: {exception}', [
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$connection = $this->createConnection($new);
|
||||
$this->server->notify(Server::EVENT_SOCKET_CONNECT, [$new, $connection]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection from a socket resource
|
||||
* The create connection object is based on the options passed into the
|
||||
* constructor ('connection_class', 'connection_options'). This connection
|
||||
* instance and its associated socket resource are then stored in the
|
||||
* manager.
|
||||
*
|
||||
* @param resource $resource A socket resource
|
||||
*/
|
||||
protected function createConnection($resource): Connection
|
||||
{
|
||||
$socket_class = $this->options['socket_client_class'];
|
||||
$socket_options = $this->options['socket_client_options'];
|
||||
|
||||
$connection_class = $this->options['connection_class'];
|
||||
$connection_options = $this->options['connection_options'];
|
||||
|
||||
$socket = new $socket_class($resource, $socket_options);
|
||||
$connection = new $connection_class($this, $socket, $connection_options);
|
||||
|
||||
if ($connection instanceof LoggerAwareInterface) {
|
||||
$connection->setLogger($this->logger);
|
||||
}
|
||||
|
||||
$id = $this->resourceId($resource);
|
||||
$this->resources[$id] = $resource;
|
||||
$this->connections[$id] = $connection;
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* This server makes an explicit assumption: PHP resource types may be cast
|
||||
* to a integer. Furthermore, we assume this is bijective. Both seem to be
|
||||
* true in most circumstances, but may not be guaranteed.
|
||||
* This method (and $this->getResourceId()) exist to make this assumption
|
||||
* explicit.
|
||||
*
|
||||
* This is needed on the connection manager as well as on resources.
|
||||
*
|
||||
* @param resource $resource
|
||||
*/
|
||||
protected function resourceId($resource): int
|
||||
{
|
||||
if (\is_resource($resource)) {
|
||||
return \get_resource_id($resource);
|
||||
}
|
||||
|
||||
return \spl_object_id($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process events on a client socket.
|
||||
*
|
||||
* @param resource $socket
|
||||
*/
|
||||
protected function processClientSocket($socket): void
|
||||
{
|
||||
$connection = $this->getConnectionForClientSocket($socket);
|
||||
|
||||
if (!$connection) {
|
||||
$this->logger->warning('No connection for client socket');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->server->notify(Server::EVENT_CLIENT_DATA, [$socket, $connection]);
|
||||
|
||||
$connection->process();
|
||||
} catch (CloseException $e) {
|
||||
$this->logger->notice('Client connection closed: {exception}', [
|
||||
'exception' => $e,
|
||||
]);
|
||||
$connection->close(Protocol::CLOSE_UNEXPECTED, $e->getMessage());
|
||||
} catch (WrenchException $e) {
|
||||
$this->logger->warning('Error on client socket: {exception}', [
|
||||
'exception' => $e,
|
||||
]);
|
||||
$connection->close(Protocol::CLOSE_UNEXPECTED);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$this->logger->warning('Wrong input arguments: {exception}', [
|
||||
'exception' => $e,
|
||||
]);
|
||||
$connection->close(Protocol::CLOSE_UNEXPECTED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Connection associated with the specified socket resource.
|
||||
*
|
||||
* @param resource $socket
|
||||
*/
|
||||
protected function getConnectionForClientSocket($socket): ?Connection
|
||||
{
|
||||
return $this->connections[$this->resourceId($socket)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connection manager's listening URI.
|
||||
*/
|
||||
public function getUri(): string
|
||||
{
|
||||
return $this->server->getUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Server
|
||||
*/
|
||||
public function getServer(): Server
|
||||
{
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a connection.
|
||||
*/
|
||||
public function removeConnection(Connection $connection): void
|
||||
{
|
||||
$socket = $connection->getSocket();
|
||||
|
||||
$index = $socket->getResourceId() ?? \array_search($connection, $this->connections);
|
||||
|
||||
if (false === $index) {
|
||||
$this->logger->warning('Could not remove connection: not found');
|
||||
}
|
||||
|
||||
unset($this->connections[$index]);
|
||||
unset($this->resources[$index]);
|
||||
|
||||
$this->server->notify(
|
||||
Server::EVENT_SOCKET_DISCONNECT,
|
||||
[$connection->getSocket(), $connection]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* Options include:
|
||||
* - timeout_select => int, seconds, default 0
|
||||
* - timeout_select_microsec => int, microseconds (NB: not milli), default: 200000
|
||||
*/
|
||||
protected function configure(array $options): void
|
||||
{
|
||||
$options = \array_merge([
|
||||
'socket_master_class' => ServerSocket::class,
|
||||
'socket_master_options' => [],
|
||||
'socket_client_class' => ServerClientSocket::class,
|
||||
'socket_client_options' => [],
|
||||
'connection_class' => Connection::class,
|
||||
'connection_options' => [],
|
||||
'timeout_select' => self::TIMEOUT_SELECT,
|
||||
'timeout_select_microsec' => self::TIMEOUT_SELECT_MICROSEC,
|
||||
], $options);
|
||||
|
||||
parent::configure($options);
|
||||
|
||||
$this->configureMasterSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the main server socket.
|
||||
*/
|
||||
protected function configureMasterSocket(): void
|
||||
{
|
||||
$class = $this->options['socket_master_class'];
|
||||
$options = $this->options['socket_master_options'];
|
||||
$this->socket = new $class($this->server->getUri(), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all resources.
|
||||
*
|
||||
* @return array<int, resource>
|
||||
*/
|
||||
protected function getAllResources(): array
|
||||
{
|
||||
return \array_merge($this->resources, [
|
||||
$this->socket->getResourceId() => $this->socket->getResource(),
|
||||
]);
|
||||
}
|
||||
}
|
14
pandora_console/vendor/chrome-php/wrench/src/Exception/BadRequestException.php
vendored
Normal file
14
pandora_console/vendor/chrome-php/wrench/src/Exception/BadRequestException.php
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Exception;
|
||||
|
||||
use Throwable;
|
||||
use Wrench\Protocol\Protocol;
|
||||
|
||||
class BadRequestException extends HandshakeException
|
||||
{
|
||||
public function __construct(string $message = '', int $code = null, Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code ?? Protocol::HTTP_BAD_REQUEST, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Exception;
|
||||
|
||||
use Throwable;
|
||||
use Wrench\Exception\Exception as WrenchException;
|
||||
use Wrench\Protocol\Protocol;
|
||||
|
||||
/**
|
||||
* Close connection exception.
|
||||
*/
|
||||
class CloseException extends WrenchException
|
||||
{
|
||||
public function __construct(string $message = '', int $code = null, Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code ?? Protocol::CLOSE_UNEXPECTED, $previous);
|
||||
}
|
||||
}
|
7
pandora_console/vendor/chrome-php/wrench/src/Exception/ConnectionException.php
vendored
Normal file
7
pandora_console/vendor/chrome-php/wrench/src/Exception/ConnectionException.php
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Exception;
|
||||
|
||||
class ConnectionException extends Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Exception;
|
||||
|
||||
class Exception extends \Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Exception;
|
||||
|
||||
use Wrench\Exception\Exception as WrenchException;
|
||||
|
||||
class FrameException extends WrenchException
|
||||
{
|
||||
}
|
15
pandora_console/vendor/chrome-php/wrench/src/Exception/HandshakeException.php
vendored
Normal file
15
pandora_console/vendor/chrome-php/wrench/src/Exception/HandshakeException.php
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Exception;
|
||||
|
||||
use Throwable;
|
||||
use Wrench\Exception\Exception as WrenchException;
|
||||
use Wrench\Protocol\Protocol;
|
||||
|
||||
class HandshakeException extends WrenchException
|
||||
{
|
||||
public function __construct(string $message = '', int $code = null, Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code ?? Protocol::HTTP_SERVER_ERROR, $previous);
|
||||
}
|
||||
}
|
17
pandora_console/vendor/chrome-php/wrench/src/Exception/InvalidOriginException.php
vendored
Normal file
17
pandora_console/vendor/chrome-php/wrench/src/Exception/InvalidOriginException.php
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Exception;
|
||||
|
||||
use Throwable;
|
||||
use Wrench\Protocol\Protocol;
|
||||
|
||||
/**
|
||||
* Invalid origin exception.
|
||||
*/
|
||||
class InvalidOriginException extends HandshakeException
|
||||
{
|
||||
public function __construct(string $message = '', int $code = null, Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code ?? Protocol::HTTP_FORBIDDEN, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Exception;
|
||||
|
||||
use Wrench\Exception\Exception as WrenchException;
|
||||
|
||||
class PayloadException extends WrenchException
|
||||
{
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Exception;
|
||||
|
||||
use Wrench\Exception\Exception as WrenchException;
|
||||
|
||||
class SocketException extends WrenchException
|
||||
{
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
namespace Wrench\Frame;
|
||||
|
||||
use Wrench\Exception\FrameException;
|
||||
use Wrench\Protocol\Protocol;
|
||||
|
||||
/**
|
||||
* Represents a WebSocket frame.
|
||||
*/
|
||||
abstract class Frame
|
||||
{
|
||||
/**
|
||||
* The frame data length.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $length = null;
|
||||
|
||||
/**
|
||||
* The type of this payload.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $type = null;
|
||||
|
||||
/**
|
||||
* The buffer.
|
||||
*
|
||||
* May not be a complete payload, because this frame may still be receiving
|
||||
* data. See.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $buffer = '';
|
||||
|
||||
/**
|
||||
* The enclosed frame payload.
|
||||
*
|
||||
* May not be a complete payload, because this frame might indicate a continuation
|
||||
* frame. See isFinal() versus isComplete().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $payload = '';
|
||||
|
||||
/**
|
||||
* Gets the length of the payload.
|
||||
*
|
||||
* @throws FrameException
|
||||
*/
|
||||
abstract public function getLength(): int;
|
||||
|
||||
/**
|
||||
* Resets the frame and encodes the given data into it.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
abstract public function encode(string $payload, int $type = Protocol::TYPE_TEXT, bool $masked = false): self;
|
||||
|
||||
/**
|
||||
* Whether the frame is the final one in a continuation.
|
||||
*/
|
||||
abstract public function isFinal(): bool;
|
||||
|
||||
abstract public function getType(): int;
|
||||
|
||||
/**
|
||||
* Receieves data into the frame.
|
||||
*/
|
||||
public function receiveData(string $data): void
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this frame is waiting for more data.
|
||||
*/
|
||||
public function isWaitingForData(): bool
|
||||
{
|
||||
return $this->getRemainingData() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the remaining number of bytes before this frame will be complete.
|
||||
*/
|
||||
public function getRemainingData(): ?int
|
||||
{
|
||||
try {
|
||||
return $this->getExpectedBufferLength() - $this->getBufferLength();
|
||||
} catch (FrameException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expected length of the buffer once all the data has been
|
||||
* receieved.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract protected function getExpectedBufferLength(): int;
|
||||
|
||||
/**
|
||||
* Gets the expected length of the frame payload.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getBufferLength(): int
|
||||
{
|
||||
return \strlen($this->buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of the frame payload.
|
||||
*
|
||||
* The frame must be complete to call this method.
|
||||
*
|
||||
* @throws FrameException
|
||||
*/
|
||||
public function getFramePayload(): string
|
||||
{
|
||||
if (!$this->isComplete()) {
|
||||
throw new FrameException('Cannot get payload: frame is not complete');
|
||||
}
|
||||
|
||||
if (!$this->payload && $this->buffer) {
|
||||
$this->decodeFramePayloadFromBuffer();
|
||||
}
|
||||
|
||||
return $this->payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the frame is complete.
|
||||
*/
|
||||
public function isComplete(): bool
|
||||
{
|
||||
if (!$this->buffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->getBufferLength() >= $this->getExpectedBufferLength();
|
||||
} catch (FrameException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a frame payload from the buffer.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function decodeFramePayloadFromBuffer(): void;
|
||||
|
||||
/**
|
||||
* Gets the binary contents of the frame buffer.
|
||||
*
|
||||
* This is the encoded value, receieved into the frame with receiveData().
|
||||
*
|
||||
* @throws FrameException
|
||||
*/
|
||||
public function getFrameBuffer(): string
|
||||
{
|
||||
if (!$this->buffer && $this->payload) {
|
||||
throw new FrameException('Cannot get frame buffer');
|
||||
}
|
||||
|
||||
return $this->buffer;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue