initial commit of SVN release repo

This commit is contained in:
Ryan Prather
2018-05-07 10:51:08 -04:00
committed by Ryan Prather
parent 2c25d5e577
commit 8c38a6cdb9
4369 changed files with 728576 additions and 0 deletions

173
inc/array2xml.inc Normal file
View File

@ -0,0 +1,173 @@
<?php
/**
* Array2XML: A class to convert array in PHP to XML
* It also takes into account attributes names unlike SimpleXML in PHP
* It returns the XML in form of DOMDocument class for further manipulation.
* It throws exception if the tag name or attribute name has illegal chars.
*
* Author : Lalit Patel
* Website: http://www.lalit.org/lab/convert-php-array-to-xml-with-attributes
* License: Apache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0
* Version: 0.1 (10 July 2011)
* Version: 0.2 (16 August 2011)
* - replaced htmlentities() with htmlspecialchars() (Thanks to Liel Dulev)
* - fixed a edge case where root node has a false/null/0 value. (Thanks to Liel Dulev)
* Version: 0.3 (22 August 2011)
* - fixed tag sanitize regex which didn't allow tagnames with single character.
* Version: 0.4 (18 September 2011)
* - Added support for CDATA section using @cdata instead of @value.
* Version: 0.5 (07 December 2011)
* - Changed logic to check numeric array indices not starting from 0.
* Version: 0.6 (04 March 2012)
* - Code now doesn't @cdata to be placed in an empty array
* Version: 0.7 (24 March 2012)
* - Reverted to version 0.5
* Version: 0.8 (02 May 2012)
* - Removed htmlspecialchars() before adding to text node or attributes.
*
* - Mar 8, 2017 - Formatting
*
* Usage:
* $xml = Array2XML::createXML('root_node_name', $php_array);
* echo $xml->saveXML();
*/
class Array2XML {
private static $xml = null;
private static $encoding = 'UTF-8';
public static $all_caps = false;
/**
* Initialize the root XML node [optional]
* @param $version
* @param $encoding
* @param $format_output
*/
public static function init($version = '1.0', $encoding = 'UTF-8', $format_output = true) {
self::$xml = new DomDocument($version, $encoding);
self::$xml->formatOutput = $format_output;
self::$encoding = $encoding;
}
/**
* Convert an Array to XML
* @param string $node_name - name of the root node to be converted
* @param array $arr - array to be converted
* @return DomDocument
*/
public static function &createXML($node_name, $arr = array()) {
$xml = self::getXMLRoot();
$xml->appendChild(self::convert($node_name, $arr));
self::$xml = null; // clear the xml node in the class for 2nd time use.
return $xml;
}
/**
* Convert an Array to XML
* @param string $node_name - name of the root node to be converted
* @param array $arr - array to be converted
* @return DOMNode
*/
private static function &convert($node_name, $arr = array()) {
//print_arr($node_name);
$xml = self::getXMLRoot();
$node = $xml->createElement($node_name);
if (is_array($arr)) {
// get the attributes first.;
if (isset($arr['@attributes'])) {
foreach ($arr['@attributes'] as $key => $value) {
if (!self::isValidTagName($key)) {
throw new Exception('[Array2XML] Illegal character in attribute name. attribute: ' . $key . ' in node: ' . $node_name);
}
$node->setAttribute($key, self::bool2str($value));
}
unset($arr['@attributes']); //remove the key from the array once done.
}
// check if it has a value stored in @value, if yes store the value and return
// else check if its directly stored as string
if (isset($arr['@value'])) {
$node->appendChild($xml->createTextNode(self::bool2str($arr['@value'])));
unset($arr['@value']); //remove the key from the array once done.
//return from recursion, as a note with value cannot have child nodes.
return $node;
}
else if (isset($arr['@cdata'])) {
$node->appendChild($xml->createCDATASection(self::bool2str($arr['@cdata'])));
unset($arr['@cdata']); //remove the key from the array once done.
//return from recursion, as a note with cdata cannot have child nodes.
return $node;
}
}
//create subnodes using recursion
if (is_array($arr)) {
// recurse to get the node for that key
foreach ($arr as $key => $value) {
if (!self::isValidTagName($key)) {
throw new Exception('[Array2XML] Illegal character in tag name. tag: ' . $key . ' in node: ' . $node_name);
}
$key = self::$all_caps ? strtoupper($key) : $key;
if (is_array($value) && is_numeric(key($value))) {
// MORE THAN ONE NODE OF ITS KIND;
// if the new array is numeric index, means it is array of nodes of the same kind
// it should follow the parent key name
foreach ($value as $k => $v) {
$node->appendChild(self::convert($key, $v));
}
}
else {
// ONLY ONE NODE OF ITS KIND
$node->appendChild(self::convert($key, $value));
}
unset($arr[$key]); //remove the key from the array once done.
}
}
// after we are done with all the keys in the array (if it is one)
// we check if it has any text value, if yes, append it.
if (!is_array($arr)) {
$node->appendChild($xml->createTextNode(self::bool2str($arr)));
}
return $node;
}
/*
* Get the root XML node, if there isn't one, create it.
*/
private static function getXMLRoot() {
if (empty(self::$xml)) {
self::init();
}
return self::$xml;
}
/*
* Get string representation of boolean value
*/
private static function bool2str($v) {
//convert boolean to text value.
$v = $v === true ? 'true' : $v;
$v = $v === false ? 'false' : $v;
return $v;
}
/*
* Check if the tag name or attribute name contains illegal characters
* Ref: http://www.w3.org/TR/xml/#sec-common-syn
*/
private static function isValidTagName($tag) {
$pattern = '/^[a-z_]+[a-z0-9\:\-\.\_]*[^:]*$/i';
return preg_match($pattern, $tag, $matches) && $matches[0] == $tag;
}
}

85
inc/base.inc Normal file
View File

@ -0,0 +1,85 @@
<?php
/**
* File: base.inc
* Author: Ryan Prather
* Purpose: The purpose of this file is to hold any constants that are used throughout the program
* Created: 6 Jan 2014
*
* Portions Copyright (c) 2012-2015, Salient Federal Solutions
* Portions Copyright (c) 2008-2011, Science Applications International Corporation (SAIC)
* Released under Modified BSD License
*
* See license.txt for details
*
* Change Log:
* - 6 Jan 14 - File created
*/
/**
* Public array to store the states for drop downs, etc
*/
$STATES = array(
'' => ' -- Please Select An Option -- ',
'AL' => 'Alabama',
'AK' => 'Alaska',
'AZ' => 'Arizona',
'AR' => 'Arkansas',
'CA' => 'California',
'CO' => 'Colorado',
'CT' => 'Connecticut',
'DE' => 'Deleware',
'DC' => 'District of Columbia',
'FL' => 'Florida',
'GA' => 'Georgia',
'HI' => 'Hawaii',
'ID' => 'Idaho',
'IL' => 'Illinois',
'IN' => 'Indiana',
'IA' => 'Iowa',
'KS' => 'Kansas',
'KY' => 'Kentucky',
'LA' => 'Lousiana',
'ME' => 'Maine',
'MD' => 'Maryland',
'MA' => 'Massachusetts',
'MI' => 'Michigan',
'MN' => 'Minnesota',
'MS' => 'Mississippi',
'MO' => 'Missouri',
'MT' => 'Montana',
'NE' => 'Nebraska',
'NH' => 'New Hampshire',
'NJ' => 'New Jersey',
'NM' => 'New Mexico',
'NV' => 'Nevada',
'NY' => 'New York',
'NC' => 'North Carolina',
'ND' => 'North Dakota',
'OH' => 'Ohio',
'OK' => 'Oklahoma',
'OR' => 'Oregon',
'PA' => 'Pennsylvania',
'RI' => 'Rhode Island',
'SC' => 'South Carolina',
'SD' => 'South Dakota',
'TN' => 'Tennessee',
'TX' => 'Texas',
'UT' => 'Utah',
'VA' => 'Virgina',
'VT' => 'Vermont',
'WA' => 'Washington',
'WV' => 'West Virgina',
'WI' => 'Wisconsin',
'WY' => 'Wyoming'
);
/**
* Public array to store countries where we have systems
*/
$Countries = array(
'' => ' -- Please Select An Option -- ',
'AI' => 'Ascension Island',
'KI' => 'Kwajalein Island',
'DG' => 'Diego Garcia',
'US' => 'United States'
);

46
inc/chart.php Normal file
View File

@ -0,0 +1,46 @@
<?php
/**
* File: chart.php
* Author: Ryan Prather
* Purpose: {purpose of file}
* Created: Nov 13, 2014
*
* Portions Copyright (c) 2012-2015, Salient Federal Solutions
* Portions Copyright (c) 2008-2011, Science Applications International Corporation (SAIC)
* Released under Modified BSD License
*
* See license.txt for details
*
* Change Log:
* - Nov 13, 2014 - File created
*/
include_once 'pChart/class/pBubble.class.php';
include_once 'pChart/class/pCache.class.php';
include_once 'pChart/class/pData.class.php';
include_once 'pChart/class/pDraw.class.php';
include_once 'pChart/class/pImage.class.php';
include_once 'pChart/class/pIndicator.class.php';
include_once 'pChart/class/pPie.class.php';
include_once 'pChart/class/pRadar.class.php';
include_once 'pChart/class/pScatter.class.php';
include_once 'pChart/class/pSplit.class.php';
if (!extension_loaded('gd') && !extension_loaded('gd2')) {
die("GD extensions not loaded");
}
$chart = new pData();
$chart->addPoints(array(0,3.5,4,3,5));
$pic = new pImage(700, 250, $chart);
$pic->setGraphArea(60, 40, 670, 190);
$pic->setFontProperties(array("FontName"=>"pChart/fonts/verdana.ttf","FontSize"=>11));
$pic->drawScale();
$pic->drawLineChart();
$pic->stroke();

12
inc/composer.json Normal file
View File

@ -0,0 +1,12 @@
{
"require" : {
"phpoffice/phpspreadsheet" : "^1.0",
"cocur/background-process" : "^0.7.0",
"tecnickcom/tcpdf" : "^6.2",
"pacificsec/cpe" : "^1.0",
"monolog/monolog": "^1.23"
},
"require-dev" : {
"phpunit/phpunit" : "^6.2"
}
}

396
inc/composer.lock generated Normal file
View File

@ -0,0 +1,396 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "41dfdd5cc44f13ac7c1263825209cec7",
"packages": [
{
"name": "cocur/background-process",
"version": "v0.7",
"source": {
"type": "git",
"url": "https://github.com/cocur/background-process.git",
"reference": "9ae2902922f02ec5544d723756758eb7304c6869"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cocur/background-process/zipball/9ae2902922f02ec5544d723756758eb7304c6869",
"reference": "9ae2902922f02ec5544d723756758eb7304c6869",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "~4.8|~5.1"
},
"type": "library",
"autoload": {
"psr-4": {
"Cocur\\BackgroundProcess\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Start processes in the background that continue running when the PHP process exists.",
"keywords": [
"background",
"process",
"unix"
],
"time": "2017-02-11T12:41:41+00:00"
},
{
"name": "monolog/monolog",
"version": "1.23.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
"reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"psr/log": "~1.0"
},
"provide": {
"psr/log-implementation": "1.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"graylog2/gelf-php": "~1.0",
"jakub-onderka/php-parallel-lint": "0.9",
"php-amqplib/php-amqplib": "~2.4",
"php-console/php-console": "^3.1.3",
"phpunit/phpunit": "~4.5",
"phpunit/phpunit-mock-objects": "2.3.0",
"ruflin/elastica": ">=0.90 <3.0",
"sentry/sentry": "^0.13",
"swiftmailer/swiftmailer": "^5.3|^6.0"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongo": "Allow sending log messages to a MongoDB server",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"php-console/php-console": "Allow sending log messages to Google Chrome",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
"sentry/sentry": "Allow sending log messages to a Sentry server"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.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": "http://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "http://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"time": "2017-06-19T01:22:40+00:00"
},
{
"name": "pacificsec/cpe",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/pacificsec/cpe.git",
"reference": "3d78d66fc4ea249b6f353a7c48f426835a792d11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pacificsec/cpe/zipball/3d78d66fc4ea249b6f353a7c48f426835a792d11",
"reference": "3d78d66fc4ea249b6f353a7c48f426835a792d11",
"shasum": ""
},
"type": "library",
"notification-url": "https://packagist.org/downloads/"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "a2771e562e3a17c0d512d2009e38fd628beece90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/a2771e562e3a17c0d512d2009e38fd628beece90",
"reference": "a2771e562e3a17c0d512d2009e38fd628beece90",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"php": "^5.6|^7.0",
"psr/simple-cache": "^1.0"
},
"require-dev": {
"dompdf/dompdf": "^0.8.0",
"friendsofphp/php-cs-fixer": "@stable",
"jpgraph/jpgraph": "^4.0",
"mpdf/mpdf": "^7.0.0",
"phpunit/phpunit": "^5.7",
"squizlabs/php_codesniffer": "^2.7",
"tecnickcom/tcpdf": "^6.2"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-dom": "Option to read and write HTML files",
"ext-gd": "Required for exact column width autocalculation",
"jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnick.com/tcpdf": "Option for rendering PDF with PDF Writer"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "http://blog.maartenballiauw.be"
},
{
"name": "Erik Tilt"
},
{
"name": "Franck Lefevre",
"homepage": "http://rootslabs.net"
},
{
"name": "Mark Baker",
"homepage": "http://markbakeruk.net"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"time": "2018-01-28T12:37:15+00:00"
},
{
"name": "psr/log",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2016-10-10T12:19:37+00:00"
},
{
"name": "psr/simple-cache",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/simple-cache.git",
"reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/753fa598e8f3b9966c886fe13f370baa45ef0e24",
"reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\SimpleCache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interfaces for simple caching",
"keywords": [
"cache",
"caching",
"psr",
"psr-16",
"simple-cache"
],
"time": "2017-01-02T13:31:39+00:00"
},
{
"name": "tecnickcom/tcpdf",
"version": "6.2.17",
"source": {
"type": "git",
"url": "https://github.com/tecnickcom/TCPDF.git",
"reference": "64fc19439863e1b1314487a72a74d9bfd0b55a53"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/64fc19439863e1b1314487a72a74d9bfd0b55a53",
"reference": "64fc19439863e1b1314487a72a74d9bfd0b55a53",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"autoload": {
"classmap": [
"config",
"include",
"tcpdf.php",
"tcpdf_parser.php",
"tcpdf_import.php",
"tcpdf_barcodes_1d.php",
"tcpdf_barcodes_2d.php",
"include/tcpdf_colors.php",
"include/tcpdf_filters.php",
"include/tcpdf_font_data.php",
"include/tcpdf_fonts.php",
"include/tcpdf_images.php",
"include/tcpdf_static.php",
"include/barcodes/datamatrix.php",
"include/barcodes/pdf417.php",
"include/barcodes/qrcode.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"authors": [
{
"name": "Nicola Asuni",
"email": "info@tecnick.com",
"role": "lead"
}
],
"description": "TCPDF is a PHP class for generating PDF documents and barcodes.",
"homepage": "http://www.tcpdf.org/",
"keywords": [
"PDFD32000-2008",
"TCPDF",
"barcodes",
"datamatrix",
"pdf",
"pdf417",
"qrcode"
],
"time": "2018-02-24T11:48:20+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}

12613
inc/database.inc Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,375 @@
<?php
/**
* File: excelConditionalStyles.inc
* Author: Ryan Prather
* Purpose: The purpose of this file is hold any constants or variables that will be used in creating an eChecklist for export
* Created: 6 Jan 2014
*
* Portions Copyright 2016-2017: Cyber Perspectives, All rights reserved
* Released under the Apache v2.0 License
*
* Portions Copyright (c) 2012-2015, Salient Federal Solutions
* Portions Copyright (c) 2008-2011, Science Applications International Corporation (SAIC)
* Released under Modified BSD License
*
* See license.txt for details
*
* Change Log:
* - Jan 6, 2014 - File created
* - Mar 10, 2017 - Formatting, added Cyber Perspectives copyright, and added $light_gray variable color
* - May 13, 2017 - Updated to use new PHPSpreadsheet library
* - Dec 27, 2017 - Syntax updates
*/
$conditions = array(
'unclass_classification' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'fouo_classification' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'secret_classification' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'exception' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'false_positive' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'nf_na_conflict' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'open_conflict' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'open_cat_1_count' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'open_cat_2_count' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'open_cat_3_count' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'not_a_finding' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'true' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'not_applicable' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'not_reviewed' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'no_data' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'open' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'false' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'cat_1' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'cat_2' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'cat_3' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'not_a_finding_count' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'not_applicable_count' => new \PhpOffice\PhpSpreadsheet\Style\Conditional(),
'not_reviewed_count' => new \PhpOffice\PhpSpreadsheet\Style\Conditional()
);
$styles = array(
'header' => array(
'bold' => true
),
'right' => array(
'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT
),
'yellow_fill' => array(
'fill' => array(
'type' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startcolor' => array(
'rgb' => 'FFFF00'
)
)
)
);
$validation = array(
'host_status' => new \PhpOffice\PhpSpreadsheet\Cell\DataValidation(),
'true_false' => new \PhpOffice\PhpSpreadsheet\Cell\DataValidation()
);
$red = new \PhpOffice\PhpSpreadsheet\Style\Color();
$red->setARGB('FFFF0000');
$green = new \PhpOffice\PhpSpreadsheet\Style\Color();
$green->setARGB('FF92D050');
$light_blue = new \PhpOffice\PhpSpreadsheet\Style\Color();
$light_blue->setARGB('FFC5D9F1');
$yellow = new \PhpOffice\PhpSpreadsheet\Style\Color();
$yellow->setARGB('FFFFFF00');
$light_yellow = new \PhpOffice\PhpSpreadsheet\Style\Color();
$light_yellow->setARGB('FFFFFF66');
$orange = new \PhpOffice\PhpSpreadsheet\Style\Color();
$orange->setARGB('FFFFC000');
$black = new \PhpOffice\PhpSpreadsheet\Style\Color();
$black->setARGB('00000000');
$white = new \PhpOffice\PhpSpreadsheet\Style\Color();
$white->setARGB('FFFFFFFF');
$light_brown = new \PhpOffice\PhpSpreadsheet\Style\Color();
$light_brown->setRGB('DDD9C4');
$light_gray = new \PhpOffice\PhpSpreadsheet\Style\Color();
$light_gray->setRGB('D6D4CA');
$conditions['unclass_classification']->setConditionType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['unclass_classification']->setOperatorType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['unclass_classification']->setText('UNCLASSIFIED');
$conditions['unclass_classification']->getStyle()
->getFont()
->setColor($green)
->setBold(true);
$conditions['fouo_classification']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['fouo_classification']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['fouo_classification']->setText('UNCLASSIFIED//FOUO');
$conditions['fouo_classification']->getStyle()
->getFont()
->setColor($orange)
->setBold(true);
$conditions['secret_classification']->setConditionType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['secret_classification']->setOperatorType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['secret_classification']->setText('SECRET');
$conditions['secret_classification']->getStyle()
->getFont()
->setColor($red)
->setBold(true);
$conditions['open']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['open']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['open']->setText('Open');
$conditions['open']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['open']->getStyle()
->getFill()
->setEndColor($red);
$conditions['open']->getStyle()
->getFont()
->setColor($white);
$conditions['false']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['false']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['false']->setText('FALSE');
$conditions['false']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['false']->getStyle()
->getFill()
->setEndColor($red);
$conditions['exception']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['exception']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['exception']->setText('Exception');
$conditions['exception']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['exception']->getStyle()
->getFill()
->setEndColor($red);
$conditions['exception']->getStyle()
->getFont()
->setColor($white);
$conditions['cat_1']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CELLIS);
$conditions['cat_1']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_EQUAL);
$conditions['cat_1']->addCondition('"I"');
$conditions['cat_1']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['cat_1']->getStyle()
->getFill()
->setEndColor($red);
$conditions['cat_1']->getStyle()
->getFont()
->setColor($white);
$conditions['cat_2']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CELLIS);
$conditions['cat_2']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_EQUAL);
$conditions['cat_2']->addCondition('"II"');
$conditions['cat_2']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['cat_2']->getStyle()
->getFill()
->setEndColor($light_yellow);
$conditions['cat_3']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CELLIS);
$conditions['cat_3']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_EQUAL);
$conditions['cat_3']->addCondition('"III"');
$conditions['cat_3']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['cat_3']->getStyle()
->getFill()
->setEndColor($orange);
$conditions['no_data']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['no_data']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['no_data']->setText('No Data');
$conditions['no_data']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['no_data']->getStyle()
->getFill()
->setEndColor($black);
$conditions['no_data']->getStyle()
->getFont()
->setColor($white);
$conditions['nf_na_conflict']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['nf_na_conflict']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['nf_na_conflict']->setText('NF/NA CONFLICT:');
$conditions['nf_na_conflict']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['nf_na_conflict']->getStyle()
->getFill()
->setEndColor($light_blue);
$conditions['open_conflict']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['open_conflict']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['open_conflict']->setText('OPEN CONFLICT:');
$conditions['open_conflict']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['open_conflict']->getStyle()
->getFill()
->setEndColor($red);
$conditions['open_conflict']->getStyle()
->getFont()
->setColor($white);
$conditions['not_a_finding']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['not_a_finding']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['not_a_finding']->setText('Not a Finding');
$conditions['not_a_finding']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['not_a_finding']->getStyle()
->getFill()
->setEndColor($green);
$conditions['true']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['true']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['true']->setText('TRUE');
$conditions['true']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['true']->getStyle()
->getFill()
->setEndColor($green);
$conditions['false_positive']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['false_positive']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['false_positive']->setText('False Positive');
$conditions['false_positive']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['false_positive']->getStyle()
->getFill()
->setEndColor($green);
$conditions['not_applicable']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['not_applicable']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['not_applicable']->setText('Not Applicable');
$conditions['not_applicable']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['not_applicable']->getStyle()
->getFill()
->setEndColor($light_blue);
$conditions['not_reviewed']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CONTAINSTEXT);
$conditions['not_reviewed']->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_CONTAINSTEXT);
$conditions['not_reviewed']->setText('Not Reviewed');
$conditions['not_reviewed']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['not_reviewed']->getStyle()
->getFill()
->setEndColor($yellow);
$conditions['open_cat_1_count']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_EXPRESSION);
$conditions['open_cat_1_count']->setOperatorType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_GREATERTHANOREQUAL);
$conditions['open_cat_1_count']->setText('1');
$conditions['open_cat_1_count']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['open_cat_1_count']->getStyle()
->getFill()
->setEndColor($red);
$conditions['open_cat_1_count']->getStyle()
->getFont()
->setColor($white);
$conditions['open_cat_2_count']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_EXPRESSION);
$conditions['open_cat_2_count']->setOperatorType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_GREATERTHANOREQUAL);
$conditions['open_cat_2_count']->setText('1');
$conditions['open_cat_2_count']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['open_cat_2_count']->getStyle()
->getFill()
->setEndColor($yellow);
$conditions['open_cat_3_count']->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_EXPRESSION);
$conditions['open_cat_3_count']->setOperatorType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_GREATERTHANOREQUAL);
$conditions['open_cat_3_count']->setText('1');
$conditions['open_cat_3_count']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['open_cat_3_count']->getStyle()
->getFill()
->setEndColor($orange);
$conditions['not_a_finding_count']->setConditionType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_EXPRESSION);
$conditions['not_a_finding_count']->setOperatorType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_GREATERTHANOREQUAL);
$conditions['not_a_finding_count']->setText('1');
$conditions['not_a_finding_count']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['not_a_finding_count']->getStyle()
->getFill()
->setEndColor($green);
$conditions['not_applicable_count']->setConditionType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_EXPRESSION);
$conditions['not_applicable_count']->setOperatorType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_GREATERTHANOREQUAL);
$conditions['not_applicable_count']->setText('1');
$conditions['not_applicable_count']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['not_applicable_count']->getStyle()
->getFill()
->setEndColor($light_blue);
$conditions['not_reviewed_count']->setConditionType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_EXPRESSION);
$conditions['not_reviewed_count']->setOperatorType(
\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_GREATERTHANOREQUAL);
$conditions['not_reviewed_count']->setText('1');
$conditions['not_reviewed_count']->getStyle()
->getFill()
->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditions['not_reviewed_count']->getStyle()
->getFill()
->setEndColor($yellow);
$validation['host_status']->setType(\PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_LIST);
$validation['host_status']->setFormula1("=ValidStatus");
$validation['host_status']->setShowDropDown(true);
$validation['true_false']->setType(\PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_LIST);
$validation['true_false']->setFormula1("=TRUE,FALSE");
$validation['true_false']->setShowDropDown(true);
/**
* Add borders to cells
*/
$borders = [
'borders' => [
'allborders' => [
'style' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
'color' => [
'rgb' => '000000'
]
]
]
];

318
inc/finding-filter.inc Normal file
View File

@ -0,0 +1,318 @@
<?php
/**
* File: finding-filter.php
* Author: Ryan
* Purpose: This file contains all that is necessary for the display of the finding filter.
* When filtering, the results go into a dive with the id='finding-filter-results', place wherever desired.
* Created: Sep 3, 2016
*
* Copyright 2016: Cyber Perspectives, All rights reserved
* Released under the Apache v2.0 License
*
* See license.txt for details
*
* Change Log:
* - Sep 3, 2016 - File created
* - Mar 4, 2017 - Changed AJAX to use /ajax.php instead of /cgi-bin/ajax.php
*/
include_once 'database.inc';
if (!$db) {
$db = new db();
}
$filters = $db->get_Filters('finding');
$col = 250;
$col2 = 398;
if (isset($finding_filter_width)) {
$finding_filter_width -= 40;
$col = floor($finding_filter_width / 5);
$col2 = $col * 2;
}
else {
$finding_filter_width = 1200;
}
?>
<script type='text/javascript'>
function save_finding_filter() {
var criteria = '';
$('#filter option').each(function () {
criteria += $(this).text() + "\n";
});
$.post(
'/ajax.php',
{
'action': 'save_filter',
'criteria': criteria,
'type': 'finding',
'name': $('#filter-name').val(),
},
save_filter_result,
'text'
);
}
function save_filter_result(data) {
if (data == 'false') {
alert('Filter saving failed');
}
}
function add_filter() {
if ($('#filter-options').val() == '0') {
alert('Must select a filter option');
return;
}
var op = ' = ';
var op_str = ' IS ';
if ($('#not').is(':checked') && $('#like').is(':checked')) {
op = ' !~ ';
op_str = ' NOT LIKE ';
}
else if ($('#not').is(':checked')) {
op = ' != ';
op_str = ' NOT EQUAL ';
}
else if ($('#like').is(':checked')) {
op = ' ~= ';
op_str = ' LIKE ';
}
var filter = '';
switch ($('#filter-options').val()) {
default:
filter = $('#filter-text').val();
}
$('#filter').append($('<option>', {
text: $('#filter-options option:selected').text() +
op + '\'' + filter + '\'',
title: $('#filter-options option:selected').text() +
op_str + '\'' + filter + '\''
}));
filter_clean_up();
}
if (typeof window.collapse_expand_data === 'undefined') {
window.collapse_expand_data = function (selection) {
if ($('#' + selection + '-img').attr('src') == '/img/right-arrow.png') {
$('#' + selection + '-img').attr('src', '/img/down-arrow.png');
}
else {
$('#' + selection + '-img').attr('src', '/img/right-arrow.png');
}
$('#' + selection).toggle(300);
};
}
function execute_filter() {
if ($('#filter option').length == 0) {
alert('Please add something to filter');
console.error('Nothing to filter');
return;
}
var criteria = '';
$('#filter option').each(function () {
criteria += $(this).text() + "\n";
});
$.post(
'/ajax.php',
{
action: 'finding-filter',
'criteria': criteria,
start_count: $('#filter-start').val(),
count: $('#filter-count').val(),
},
display_finding_filter_results,
'html'
);
}
function display_finding_filter_results(data) {
if ($('#finding-filter-results').length == 0) {
console.error("Cannot find div to populate targets in");
return;
}
var odd = true;
$('#finding-filter-results').html("");
//$('#filter-start').val(parseInt($('#filter-start').val())+parseInt($('#filter-count').val()));
$(data).find("finding").each(function () {
odd = !odd;
});
}
function retrieve_saved_filter() {
$('#filter-start').val(0);
$.post(
'/ajax.php',
{
action: 'get-saved-filter',
'type': 'finding',
name: $('#saved-filter').val(),
},
display_saved_filter,
'text'
);
}
function display_saved_filter(data) {
$('#filter').html(data);
}
function change_filter_option() {
$('.filter').hide();
switch ($('#filter-options').val()) {
default:
$('#filter-text').show();
}
}
function filter_clean_up() {
$('#filter-start').val(0);
$('#filter-options').val(0);
$('#filter-text').val('');
$('#sw-filter').val('');
$('#like').attr('checked', false);
$('#not').attr('checked', false);
$('#sw-filter,#availableSoftware,.filter').hide();
$('#filter-text,#filter').show();
}
</script>
<style type='text/css'>
.title {
width: <?php print $finding_filter_width; ?>px;
background-color: #808080;
font-size: 14pt;
font-weight: bolder;
text-decoration: italic;
text-align: left;
padding-left: 20px;
color: black;
margin-top: 5px;
border: solid 1px black;
}
.col {
width: <?php print $col - 10; ?>px;
margin: 5px;
height: 108px;
display: inline-block;
vertical-align: top;
}
.col2 {
width: <?php print $col2 - 10; ?>px;
margin: 5px;
height: 108px;
display: inline-block;
vertical-align: top;
}
#load-more {
width: 100%;
text-align: center;
background-color: #808080;
display: none;
}
#load-more a {
color: #fff;
font-size: 18px;
text-decoration: none;
}
.table-header {
width: <?php print($finding_filter_width += 22); ?>px;
text-align: center;
}
#finding-filter-results {
width: <?php print $finding_filter_width; ?>px;
}
</style>
<div class='title'>
<img id='cat-filter-img' src='/img/right-arrow.png' onclick="javascript:collapse_expand_data('cat-filter');" style='width:20px;' />
&nbsp;&nbsp;Finding Filter...
</div>
<div id='cat-filter' style='display:none;'>
<input type='hidden' id='filter-start' value='0' />
<div class='col'>
<select id='filter-options' onchange="javascript:change_filter_option();" style='width:175px;'>
<option value='0'>Filter options...</option>
<option value='target'>Target</option>
<option value='stig'>STID ID</option>
<option value='cve'>CVE</option>
<option value='status'>Status</option>
<option value='cpe'>CPE</option>
<option value='cc'>Check Contents</option>
<option value='st'>Short Title</option>
<option value='desc'>Description</option>
<option value='notes'>Notes</option>
</select><br />
<input type='text' class='filter' id='filter-text' placeholder='Filter...' /><br />
<label for='not'>Not?</label>
<input type='checkbox' id='not' value='1' />
<label for='like'>Like?</label>
<input type='checkbox' id='like' value='1' />
<input type='button' id='add' value='Add' onclick="javascript:add_filter();" />
</div>
<div class='col2'>
<select name='filter[]' id='filter' multiple size='4' style="width:<?php print $col2 - 15; ?>px;height:110px;" title="Double-click to remove filter" ondblclick="$('#filter option:selected').remove();">
</select>
</div>
<div class='col' style='text-align: center;'>
<select id='filter-count'>
<option value='0'>Filter Count</option>
<option value='5'>5</option>
<option value='10'>10</option>
<option value='25'>25</option>
<option value='50'>50</option>
<option value='100'>100</option>
<option value='all'>All</option>
</select><br />
<input type='button' name='run-filter' value='Filter...' onclick="javascript:execute_filter();" />
</div>
<div class='col' style='text-align: right;'>
<select name='saved-filter' id='saved-filter' onchange="javascript:retrieve_saved_filter();">
<option value='0'>Saved Filters...</option>
<?php
foreach ($filters as $filter) {
print "<option>" . $filter['name'] . "</option>";
}
?>
</select><br />
<input type='text' name='filter-name' id='filter-name' /><br />
<input type='button' name='save-filter' value='Save Filter' onclick="javascript:save_finding_filter();" />
</div>
</div>
<div id='finding-filter-results'></div>
<div id='load-more'>
<a href='javascript:void(0);' onclick='load_more = true;execute_filter();'>Load More...</a>
</div>

40
inc/footer.inc Normal file
View File

@ -0,0 +1,40 @@
<?php
/**
* File: footer.inc
* Author: Paul Porter
* Purpose: This file will contain all the standardized footer information
* Created: Sep 18, 2013
*
* Portions Copyright 2016: Cyber Perspectives, All rights reserved
* Released under the Apache v2.0 License
*
* Portions Copyright (c) 2012-2015, Salient Federal Solutions
* Portions Copyright (c) 2008-2011, Science Applications International Corporation (SAIC)
* Released under Modified BSD License
*
* See license.txt for details
*
* Change Log:
* - Sep 18, 2013 - File created
* - Sep 1, 2016 - Updated copyright
* - Oct 5, 2016 - Updated copyright to be a floating div
* - Nov 7, 2016 - Moved VER outside the popup text
* - Jan 30, 2017 - Extended copyright for Cyper Perspectives to 2017
* - Feb 15, 2017 - Added LLC to Cyber Perspectives entry
* - Jan 10, 2018 - Extended copyright for Cyber Perspectives to 2018
*/
?>
<div id='copyright-text'>
<p>Portions Copyright &copy; 2016-2018 Cyber Perspective, LLC All rights reserved.</p>
<p>Portions Copyright &copy; 2012-2015 Salient Federal Solutions</p>
<p>Portions Copyright &copy; 2008-2011 Science Applications International Corp.</p>
</div>
<div id="copyright" style='left:0px;'>
<a href='javascript:void(0);' onclick="$('#copyright-text').fadeToggle(200);">&copy;Copyright&copy;</a>&nbsp;&nbsp;
<a href="/docs/license.txt">License Information</a>&nbsp;&nbsp;
<a href='/help.php?topic=all' target='_blank'>User Guide</a>&nbsp;&nbsp;V<?php print (defined('VER') ? VER : ''); ?>
</div>
</body>
</html>

224
inc/header.inc Normal file
View File

@ -0,0 +1,224 @@
<?php
/**
* File: header.inc
* Author: Ryan Prather
* Purpose: This file will contain all the standardized header information
* Created: Sep 11, 2013
*
* Portions Copyright 2016: Cyber Perspectives, All rights reserved
* Released under the Apache v2.0 License
*
* Portions Copyright (c) 2012-2015, Salient Federal Solutions
* Portions Copyright (c) 2008-2011, Science Applications International Corporation (SAIC)
* Released under Modified BSD License
*
* See license.txt for details
*
* Change Log:
* - Sep 11, 2013 - File created
* - Sep 1, 2016 - Copyright and favicon updated,
* upgraded jQuery to 1.11.3 and jQuery UI to 1.11.4
* - Nov 21, 2016 - Added spin.min.js to the header
* - Feb 15, 2017 - Formatting
* - Dec 27, 2017 - Update get_Settings method to allow for an array
* - Jan 2, 2018 - Removed STIG file progress percentage
* - Jan 10, 2018 - Added fontawesome CSS library
* - Jan 15, 2018 - Added jQuery UI CSS
* - Jan 16, 2018 - Added ajax to auto update the cpe, cve, stig, and nasl loading progress.
*/
include_once 'config.inc';
include_once 'helper.inc';
include_once 'database.inc';
$db = new db();
global $title_prefix;
$msg = [];
$db->help->select_count("software");
$cpe_count = $db->help->execute();
$cpe = $db->get_Settings(['cpe-progress', 'cpe-dl-progress']);
if (!$cpe_count) {
$msg[] = "<div id='cpe-progress'>No CPE's present in DB</div>";
}
elseif (isset($cpe['cpe-dl-progress']) && between($cpe['cpe-dl-progress'], 0.01, 99.99)) {
$msg[] = "<div id='cpe-progress'>CPE Download Progress: " . number_format($cpe['cpe-dl-progress'], 0) . "%</div>";
}
elseif (isset($cpe['cpe-progress']) && between($cpe['cpe-progress'], 0.01, 99.99)) {
$msg[] = "<div id='cpe-progress'>CPE Progress: " . number_format($cpe['cpe-progress'], 0) . "%</div>";
}
$db->help->select_count("cve_db");
$cve_count = $db->help->execute();
$cve = $db->get_Settings(['cve-progress', 'cve-dl-progress']);
if (!$cve_count) {
$msg[] = "<div id='cve-progress'>No CVE's present in DB</div>";
}
elseif (isset($cve['cve-dl-progress']) && between($cve['cve-dl-progress'], 0.01, 99.99)) {
$msg[] = "<div id='cve-progress'>CVE Download Progress: " . number_format($cve['cve-dl-progress'], 0) . "%</div>";
}
elseif (isset($cve['cve-progress']) && between($cve['cve-progress'], 0.01, 99.99)) {
$msg[] = "<div id='cve-progress'>CVE Progress: " . number_format($cve['cve-progress'], 0) . "%</div>";
}
$db->help->select_count("stigs");
$stig_count = $db->help->execute();
$stig = $db->get_Settings(['stig-progress', 'stig-dl-progress']);
if (!$stig_count) {
$msg[] = "<div id='stig-progress'>No STIG's present in DB</div>";
}
elseif (isset($stig['stig-dl-progress']) && between($stig['stig-dl-progress'], 0.01, 99.99)) {
$msg[] = "<div id='stig-progress'>STIG Download Progress: " . number_format($stig['stig-dl-progress'], 0) . "%</div>";
}
elseif (isset($stig['stig-progress']) && between($stig['stig-progress'], 0.01, 99.99)) {
$msg[] = "<div id='stig-progress'>STIG Progress: " .
"<span id='stig-overall-progress'>" . number_format($stig['stig-progress'], 0) . "%</span>" .
"</div>";
}
$db->help->select_count("nessus_plugins");
$nessus_count = $db->help->execute();
$nasl_progress = $db->get_Settings(['nasl-progress', 'nasl-dl-progress']);
if (!$nessus_count) {
if (isset($nasl['nasl-dl-progress']) && between($nasl['nasl-dl-progress'], 0.01, 99.99)) {
$msg[] = "<div id='nasl-progress'>NASL Download Progress: {$nasl['nasl-dl-progress']}%</div>";
}
elseif (isset($nasl['nasl-progress']) && between($nasl['nasl-progress'], 0.01, 99.99)) {
$msg[] = "<div id='nasl-progress'>NASL Progress: {$nasl['nasl-progress']}%</div>";
}
}
?>
<!DOCTYPE HTML>
<html>
<head>
<title><?php print (isset($title_prefix) ? "$title_prefix | " : ""); ?>Sagacity</title>
<link href='/style/fonts/fonts.css' rel='stylesheet' type='text/css' />
<!--[if IE 9]><link rel="stylesheet" href="style/style-ie9.css" /><![endif]-->
<script src="/style/5grid/jquery-1.11.3.min.js"></script>
<script src="/style/5grid/jquery.browser.min.js"></script>
<script type="text/javascript" src="/script/jquery-ui-1.11.4/jquery-ui.min.js"></script>
<script src="/style/5grid/init.js?use=mobile,desktop,1000px&amp;mobileUI=1&amp;mobileUI.theme=none"></script>
<script type="text/javascript" src="/script/default.js"></script>
<script type="text/javascript" src="/script/highcharts-custom.js"></script>
<script type="text/javascript" src="/script/spin/spin.min.js"></script>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="/favicon-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="/favicon-160x160.png" sizes="160x160">
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16">
<link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32">
<link rel='stylesheet' href='/script/fontawesome-free-5.0.3/web-fonts-with-css/css/fontawesome-all.min.css' />
<link rel='stylesheet' href='/script/jquery-ui-1.11.4/jquery-ui.theme.min.css' />
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
<script type='text/javascript'>
$(function () {
if ($('#db-err')) {
getLoadStatus();
}
});
function getLoadStatus() {
$.ajax("/ajax.php", {
data: {
action: 'get-load-status'
},
success: function (data) {
var reload = false;
if($('#cpe-progress').length) {
$('#cpe-progress').html(loadValue('cpe-progress', data['cpe-dl-progress'], data['cpe-progress'], 'CPE'));
reload = true;
}
if($('#cve-progress').length) {
$('#cve-progress').html(loadValue('cve-progress', data['cve-dl-progress'], data['cve-progress'], 'CVE'));
reload = true;
}
if($('#stig-progress').length) {
$('#stig-progress').html(loadValue('stig-progress', data['stig-dl-progress'], data['stig-progress'], 'STIG'));
reload = true;
}
if($('#nasl-progress').length) {
$('#nasl-progress').html(loadValue('nasl-progress', data['nasl-dl-progress'], data['nasl-progress'], 'NASL'));
reload = true;
}
if (reload) {
setTimeout(getLoadStatus, 1000);
}
else {
$('#db-err').remove();
}
},
error: function (xhr, status, error) {
console.error(error);
},
dataType: 'json',
method: 'post'
});
}
function loadValue(id, dl, prog, msg) {
if(dl < 100 && prog == 0) {
return msg + ' Download Progress ' + parseInt(dl) + "%";
}
else if(prog < 100) {
return msg + ' Progress ' + parseInt(prog) + "%";
}
else {
$('#' + id).remove();
}
}
</script>
</head>
<body>
<?php
if (count($msg)) {
print "<div id='db-err' style='width:100%;text-align:center;font-size:14pt;background-color:red;color:white;'>" .
implode("", $msg) .
"</div>";
}
?>
<div id="header-wrapper">
<header id="header" class="5grid-layout">
<div class="row">
<div class="12u">
<!-- Nav -->
<?php include_once 'menu.inc'; ?>
<!-- Logo -->
<span class="mobileUI-site-name">
<img src='/img/Sagacity-Logo.png' style='width:210px;float:right;' />
</span>
</div>
</div>
</header>
</div>

784
inc/helper.inc Normal file
View File

@ -0,0 +1,784 @@
<?php
/**
* File: helper.inc
* Author: Ryan Prather
* Purpose: The purpose of this file is to hold any helper functions that are used in various locations in the application
* Created: 6 Jan 2014
*
* Portions Copyright 2016-2017: Cyber Perspectives, All rights reserved
* Released under the Apache v2.0 License
*
* Portions Copyright (c) 2012-2015, Salient Federal Solutions
* Portions Copyright (c) 2008-2011, Science Applications International Corporation (SAIC)
* Released under Modified BSD License
*
* See license.txt for details
*
* Change Log:
* - 6 Jan 14 - File created
* - Sep 1, 2016 - Copyright Update and
* changed file types to string instead of index
* - Oct 24, 2016 - Added check in download_file function to ensure can open the local file
* - Nov 7, 2016 - Added check for existence of file_types class before creating
* - Dec 7, 2016 - Changed PHP constant to PHP_BIN
* - Dec 12, 2016 - Added parsing for company variables in config.xml
* - Dec 13, 2016 - Changed error in load_Config() to die if it can't find the config.xml file
* - Jan 30, 2017 - Formatting
* - Feb 15, 2017 - Added BZip, ping, between, and url_exists functions
* Migrated file_types constants to defined constants
* - Feb 21, 2017 - Added documentation, fixed issue with remote_filesize not correctly retrieving the file size of a remote file on Windows,
* Started adding download speed checker, but need to troubleshoot
* - Mar 3, 2017 - Deleted what_software method (all code is using software::identify_Software method instead)
* - Mar 13, 2017 - Cleaned up download_file and url_exists functions
* - Mar 22, 2017 - Documentation for constants, added JSON constant for header output,
* Added check for 5-digit extensions (.debug) in check_path,
* - Apr 5, 2017 - Added $chdir parameter to check_path function to change directory if set true
* - May 13, 2017 - Added ECHECKLIST_OUTPUT_FORMAT constant
* - May 19, 2017 - Added NOTIFICATIONS constant
* - Oct 2, 2017 - Fixed error with file downloads showing as moved and return largest content-size from http header
* - Oct 27, 2017 - Added UNSUPPORTED_INI constant and file detection for desktop.ini files
* - Dec 27, 2017 - Expanded download_file method to allow for download progress meta key
*/
include_once 'error.inc';
include_once 'validation.inc';
/**
* Function to get element or value from XML document using XPath
*
* @param DOMDocument $xml
* Document
* @param string $path
* XPath string
* @param string $starting [optional]
* Starting node
* @param boolean $keep [optional]
* Return the DOMElement
*
* @return mixed
* Will a DOMElement if $keep is true, a string if a single element, or an array:string if there are multiple elements
*/
function getValue($xml, $path, $starting = null, $keep = false) {
$xpath = new DOMXPath($xml);
$ns = $xml->lookupNamespaceUri($xml->namespaceURI);
$xpath->registerNamespace('x', $ns);
if (is_null($starting)) {
$buf = $xpath->query($path);
}
else {
$buf = $xpath->query($path, $starting);
}
$ret = null;
if ($keep) {
return $buf;
}
if ($buf->length == 1) {
if (get_class($buf->item(0)) == 'DOMAttr') {
$ret = $buf->item(0)->value;
}
elseif (get_class($buf->item(0)) == 'DOMElement' && $buf->item(0)->childNodes->length == 1) {
$ret = $buf->item(0)->nodeValue;
}
elseif (get_class($buf->item(0)) == 'DOMElement' && $buf->item(0)->childNodes->length > 1) {
foreach ($buf->item(0)->childNodes as $node) {
if ($node->nodeName != '#text') {
$ret[$node->nodeName][] = $node->nodeValue;
}
}
}
}
elseif ($buf->length > 1) {
foreach ($buf as $node) {
if ($node->childNodes->length == 1) {
$ret[] = $node->nodeValue;
}
elseif ($node->childNodes->length > 1) {
foreach ($node->childNodes as $childNode) {
if ($childNode->nodeName != '#text') {
$ret[$childNode->nodeName][] = $childNode->nodeValue;
}
}
}
}
}
return $ret;
}
/**
* Function to determine a file type
*
* @param string $filename
* Path to file being evaulated
*
* @return array
* <p><i>type</i> - file_types class enum</p>
* <p><i>msg</i> - string notice</p>
* <p><i>base_name</i> - string file name alone</p>
*/
function FileDetection($filename) {
$name['base_name'] = basename($filename);
// print "\tCheck if exists".PHP_EOL;
if (!file_exists($filename)) {
$name['type'] = "ERROR";
$name['msg'] = "File not found";
return $name;
}
// print "\tCheck if dir".PHP_EOL;
if (is_dir($filename)) {
$name['type'] = DIRECTORY;
return $name;
}
// print "\tStarting".PHP_EOL;
$name['type'] = UNSUPPORTED; // if we can't find it - it stays unsupported
if (preg_match("/desktop\.ini/", $filename)) {
$name['type'] = UNSUPPORTED_INI;
$name['msg'] = 'Unsupported INI';
return $name;
}
// check file extension
if (preg_match('/\.xml$/i', $name['base_name'])) {
// STIG XCCDF or SCC Results or XML Nmap
if (preg_match('/SCC.*XCCDF\-Results.*\.xml/i', $name['base_name'])) {
$name['type'] = SCC_XCCDF;
}
elseif (preg_match('/SCC.*OVAL\-Results.*\.xml/i', $name['base_name'])) {
$name['type'] = SCC_OVAL;
}
elseif (preg_match('/STIG\-(oval|cpe\-dictionary|cpe\-oval)/i', $name['base_name'])) { // DISA STIG Checklist
$name['type'] = DISA_STIG_OVAL;
}
elseif (preg_match('/\$/', $name['base_name'])) { // Invalid DISA STIG checklist
$name['type'] = UNSUPPORTED_XML;
}
elseif (preg_match('/xccdf.*xml/i', $name['base_name'])) { // DISA STIG Checklist
$name['type'] = DISA_STIG_XML;
}
elseif (preg_match('/VMS6X\.xml|Session\.xml/i', $name['base_name'])) { // Gold Disk
$name['type'] = GOLDDISK;
}
elseif (preg_match('/MBSA/i', $name['base_name'])) {
$name['type'] = MBSA_XML;
}
elseif (preg_match("/nmap/i", $name['base_name'])) {
$name['type'] = NMAP_XML;
}
else {
// could be XML Nmap, or unknown
$name['type'] = UNSUPPORTED_XML;
$f = fopen($filename, 'r');
for ($x = 0; $x < 5; $x++) {
$line = fgets($f);
if (preg_match('/nmap\.xsl|nmaprun/i', $line)) {
$name['type'] = NMAP_XML;
break;
}
elseif (preg_match("/IMPORT_FILE/i", $line)) {
$name['type'] = MSSQL_XML;
break;
}
}
fclose($f);
}
}
elseif (preg_match('/\.nessus$/i', $name['base_name'])) { // usually starts with nessus_report
$name['type'] = NESSUS;
}
elseif (preg_match('/\.messages$/i', $name['base_name'])) {
$name['type'] = NESSUS_MESSAGES;
}
elseif (preg_match('/\.txt$/i', $name['base_name'])) {
if (preg_match('/All\-Settings|Non\-Compliance/i', $name['base_name'])) {
$name['type'] = UNSUPPORTED_SCC_TEXT;
}
elseif (preg_match('/SCC.*Error_Log/i', $name['base_name'])) {
$name['type'] = UNSUPPORTED_SCC_ERROR;
}
elseif (preg_match('/MBSA/i', $name['base_name'])) {
$name['type'] = MBSA_TEXT;
}
elseif (preg_match("/nmap/i", $name['base_name'])) {
$name['type'] = NMAP_TEXT;
}
else {
// see if it's an nmap file named .txt
$f = fopen($filename, 'r');
$line = fgets($f);
fclose($f);
if (preg_match('/Nmap/i', $line)) {
if (preg_match('/\-oN|Starting/i', $line)) {
$name['type'] = NMAP_TEXT;
}
elseif (preg_match('/\-oG/i', $line)) {
$name['type'] = NMAP_GREPABLE;
}
}
elseif (preg_match('/Script started|telnet|User access|sh conf|Using/i', $line)) {
$name['type'] = NMAP_NETWORK_DEVICE;
}
else {
$name['type'] = UNSUPPORTED_TEXT;
error_log($name['base_name'] . " is unsupported");
}
}
}
elseif (preg_match('/All\-PDI\-Catalog\.csv$/i', $name['base_name'])) {
$name['type'] = PDI_CATALOG;
}
elseif (preg_match('/\.csv$/i', $name['base_name'])) {
// E-Checklist or Retina
$f = fopen($filename, 'r');
$line = fgets($f);
fclose($f);
if (preg_match('/Checklist:|Unclassified|Secret|STIG[_| ]ID/i', $line)) {
$name['type'] = ECHECKLIST_CSV;
}
elseif (preg_match('/^\"NetBIOSName|^\"JobName/', $line)) {
$name['type'] = UNSUPPORTED_RETINA_CSV;
}
else {
$name['type'] = UNSUPPORTED_CSV;
}
}
elseif (preg_match('/\.xlsx$|\.xls$/i', $name['base_name'])) {
// Could be procedural or technical E-Checklist
if (preg_match('/e\-?checklist/i', $name['base_name'])) {
$name['type'] = TECH_ECHECKLIST_EXCEL;
}
elseif (preg_match('/validation|ia[\-]*controls/i', $name['base_name'])) {
$name['type'] = PROC_ECHECKLIST_EXCEL;
}
else {
$name['type'] = UNSUPPORTED_EXCEL;
}
}
elseif (preg_match('/benchmark.*\.zip$/i', $name['base_name'])) {
$name['type'] = DISA_STIG_BENCHMARK_ZIP;
}
elseif (preg_match('/(STIG_Library|IAVM|stig|srg).*\.zip$/i', $name['base_name'])) {
$name['type'] = DISA_STIG_LIBRARY_ZIP;
}
elseif (preg_match('/all\-2\.0\.tar\.gz$/i', $name['base_name'])) {
$name['type'] = NESSUS_PLUGIN_GZIP;
}
elseif (preg_match('/\.nasl$/i', $name['base_name'])) {
$name['type'] = NESSUS_PLUGIN_NASL;
}
elseif (preg_match('/\.ckl$/i', $name['base_name'])) {
$name['type'] = STIG_VIEWER_CKL;
}
elseif (preg_match('/\.nmap$/i', $name['base_name'])) {
$name['type'] = NMAP_TEXT;
}
elseif (preg_match('/\.nbe$/i', $name['base_name'])) {
$name['type'] = UNSUPPORTED_NESSUS_NBE;
}
elseif (preg_match('/\.Result$|\.log$|\.Examples$/i', $name['base_name'])) {
$name['type'] = UNSUPPORTED_UNIX_SRR;
}
if ($name['type'] == UNSUPPORTED) {
$name['msg'] = 'Unsupported File Type';
}
return $name;
}
/**
* Convert a string memory notation (1024K, 10M, 1G) to an integer byte
*
* @param string $val
* Pass in value of memory. Either an integer or integer with a measurement (g, m, k)
*
* @return integer
* Returns the amount of member in bytes being
*/
function return_bytes($val) {
$val = trim($val);
$measurement = strtolower(substr($val, -1));
$val = (int) substr($val, 0, -1);
switch ($measurement) {
case 't':
$val *= 1024;
case 'g':
$val *= 1024;
case 'm':
$val *= 1024;
case 'k':
$val *= 1024;
}
return $val;
}
/**
* Function to query and find a target based on a hostname or IP. If not found will create the target
*
* @param mysqli $db
* The mysqli connection
* @param integer $steid
* The ST&E that we are operating in
* @param string $hostname
* The hostname of the target being searched for
* @param string $ip
* The IP address of the target being searched for
*
* @return integer
* The target ID for any targets found or created
*/
function get_a_tgt_id($db, $steid, $hostname, $ip) {
# gets a target ID for the hostname/ip combo
$tgt_id = $db->check_Target($steid, $hostname);
if (!$tgt_id) {
$tgt_id = $db->check_Target($steid, $ip);
}
# If it doesn't exist, we'll have to create a target.
# OS for MBSA is Windows, but probably use generic if we have to add a host.
#$tgt_id = 0;
if (!$tgt_id) {
$sw = $db->get_Software("cpe:/o:generic:generic:-")[0];
$tgt_id = $db->save_Target('insert', array(
'ste' => $steid,
'osSoftware' => $sw->get_ID(),
'DeviceName' => $hostname,
'location' => '',
'targetNotes' => 'Created by MBSA Parser'
));
$new_int = array(
'action' => 'insert',
'tgt_id' => $tgt_id,
'ipv4' => $ip,
'hostname' => $hostname,
'fqdn' => $hostname,
);
$db->save_Interface($new_int);
}
Sagacity_Error::err_handler("Target ID: $tgt_id");
return $tgt_id;
}
/**
* Clean up passed in string
*
* @param string $string
*
* @return string
*/
function textCleanup($string) {
return htmlentities(preg_replace('/\n+/', '\n', preg_replace('/\r|\t|^\n$/', "", $string)));
}
/**
* Function to assist in the creation and appending of XML elements to an XML DOM Document
*
* @param DOMDocument $xml
* The DOMDocument to add the element to
* @param string $element_name
* The name of the element/tag to create
* @param string $element_value
* The element value
* @param boolean $is_cdata
* Is the value supposed to be inside a CData section
* @param array $attrs
* Array of name/value pair attributes
*
* @return DOMElement
* The returned element (can be passed into appendChild)
*/
function xml_helper(&$xml, $element_name, $element_value = null, $is_cdata = false, $attrs = array()) {
if (false) {
$xml = new DOMDocument();
}
if (!is_null($element_value)) {
if ($is_cdata) {
$cdata = $xml->createCDATASection($element_value);
$el = $xml->createElement($element_name);
$el->appendChild($cdata);
}
else {
$el = $xml->createElement($element_name, $element_value);
}
}
else {
$el = $xml->createElement($element_name);
}
if (is_array($attrs) && count($attrs)) {
foreach ($attrs as $name => $value) {
$el->setAttribute($name, $value);
}
}
return $el;
}
/**
* Function to log to the time log file
*
* @param resource $fh
* File handle of the file to output too
* @param string $msg
* Message to send to the file
*/
function time_log_diff($fh, $msg) {
global $last_time;
//$now = new DateTime();
$now = microtime(true);
$diff = $now - $last_time;
//if($diff > 1) {
$lt_format = DateTime::createFromFormat("U.u", $last_time);
$now_format = DateTime::createFromFormat("U.u", $now);
if (is_a($lt_format, 'DateTime') && is_a($now_format, 'DateTime')) {
fwrite($fh, $lt_format->format("H:i:s.u") . "," . $now_format->format("H:i:s.u") . ",\"$msg\"," . round($diff, 6) . PHP_EOL);
}
//}
$last_time = $now;
}
/**
* Function to download a file from a remote server
*
* @param string $url
* @param string $newname [optional]
* @param db_helper $db [optional]
* @param string $db_meta [optional]
*/
function download_file($url, $newname = null, &$db = null, $db_meta = null) {
if (!url_exists($url)) {
Sagacity_Error::err_handler("Could not find the URL $url", E_WARNING);
return;
}
$total_size = remote_filesize($url);
if (!is_null($newname)) {
$fname = $newname;
}
else {
$fname = basename($url);
}
if (!($local_fh = fopen($fname, "w"))) {
Sagacity_Error::err_handler("Failed to open the local file handle" . PHP_EOL, E_ERROR);
}
if (!($remote_fh = fopen($url, "r"))) {
Sagacity_Error::err_handler("Was not able to open the file $url", E_ERROR);
}
$written_size = 0;
print "Downloading $url (" . human_filesize($total_size) . ")" . PHP_EOL;
//$start = microtime();
while ($data = fread($remote_fh, 1048576)) {
//$end = microtime();
fwrite($local_fh, $data);
$written_size += strlen($data);
$complete = ($written_size / $total_size) * 100;
print "\rComplete " . sprintf("%.2f%%", $complete);
if (is_a($db, 'db_helper') && !is_null($db_meta) && strlen($db_meta)) {
$db->update("sagacity.settings", ['meta_value' => number_format($complete, 2)], [
[
'field' => 'meta_key',
'op' => '=',
'value' => $db_meta
]
]);
$db->execute();
}
}
print PHP_EOL;
}
/**
* To convert the byte size of a file to a human readable format
*
* @param int $bytes
* @param int $decimals
*
* @return string
*/
function human_filesize($bytes, $decimals = 2) {
$sz = 'BKMGTP';
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
}
/**
* To get the remote file size (returns bytes)
*
* @param string $url
*
* @return boolean|int
*/
function remote_filesize($url) {
error_reporting(E_ERROR);
try {
if ($size = filesize($url)) {
return $size;
}
}
catch (Exception $e) {
}
$regex = "/Content\-Length: *([\d]+)/i";
$matches = array();
if (!$fp = @fopen($url, 'rb')) {
return false;
}
if (
isset($http_response_header) &&
preg_match_all($regex, implode(PHP_EOL, $http_response_header), $matches)
) {
if (count($matches[0])) {
return (int) max($matches[1]);
}
}
print "Could not find the content-length in the header, so downloading the file to get the length" . PHP_EOL;
return strlen(stream_get_contents($fp));
}
/**
* Function to replace only the first instance of a string
*
* @param string $search
* @param string $replace
* @param string $subject
*
* @return string
*/
function str_replace_first($search, $replace, $subject) {
return implode($replace, explode($search, $subject, 2));
}
/**
* Function to check for the existence of a path and create a directory if not found
*
* @param string $path
* @param boolean $chdir
*/
function check_path($path, $chdir = false) {
if (!file_exists($path)) {
try {
// come in 4 characters from the end and check to see if it is a . (period), which signifies a file was passed in
if (strpos($path, ".") !== false && substr($path, -4, 1) == '.' || substr($path, -6, 1) == '.') {
touch($path);
}
else {
mkdir($path);
}
}
catch (Exception $e) {
die($e->getMessage());
}
}
if ($chdir && is_dir($path)) {
chdir($path);
}
}
/**
* To create a bzip file
*
* @param string $in
* @param string $out
*
* @return bool
*/
function bzip2($in, $out) {
if (!file_exists($in) || !is_readable($in))
return false;
if ((!file_exists($out) && !is_writeable(dirname($out)) || (file_exists($out) && !is_writable($out))))
return false;
if (!function_exists("bzopen"))
return false;
$in_file = fopen($in, "r");
$out_file = bzopen($out, "w");
while (!feof($in_file)) {
$buffer = fgets($in_file, 4096);
bzwrite($out_file, $buffer, 4096);
}
fclose($in_file);
bzclose($out_file);
return true;
}
/**
* To unzip a bzip file
*
* @param string $in
* @param string $out
*
* @return bool
*/
function bunzip2($in, $out) {
if (!file_exists($in) || !is_readable($in))
return false;
if ((!file_exists($out) && !is_writeable(dirname($out)) || (file_exists($out) && !is_writable($out))))
return false;
if (!function_exists("bzopen"))
return false;
$in_file = bzopen($in, "r");
$out_file = fopen($out, "w");
while ($buffer = bzread($in_file, 4096)) {
fwrite($out_file, $buffer, 4096);
}
bzclose($in_file);
fclose($out_file);
return true;
}
/**
* Function to ping a host and respond boolean
*
* @param string $host
* @param int $port
* @param int $timeout
*
* @return boolean
*/
function ping($host, $port = 80, $timeout = 6) {
$errno = null;
$errstr = null;
$fsock = @fsockopen($host, $port, $errno, $errstr, $timeout);
if (is_resource($fsock)) {
return true;
}
return false;
}
/**
* To evaluate $val and see if it is between low and high values inclusively.
*
* @param number $val
* The value to check
* @param number $low
* The minimum allowed value
* @param number $high
* The maximum allowed value
*
* @return boolean
*/
function between($val, $low, $high) {
if (is_numeric($val) && is_numeric($low) && is_numeric($high)) {
if ($val >= $low && $val <= $high) {
return true;
}
}
return false;
}
/**
* Function to check if a URL exists (run prior to downloading a file)
*
* @param string $url
*
* @return boolean
*/
function url_exists($url) {
$file_headers = get_headers($url);
foreach ($file_headers as $header) {
if (preg_match("/HTTP\/[\d\.]+ 200 OK/", $header)) {
return true;
}
}
return false;
}
/**
*
* @param type $start
* @param type $end
* @return type
*/
function microtime_diff($start, $end = null) {
list($start_usec, $start_sec) = explode(" ", $start);
list($end_usec, $end_sec) = explode(" ", $end);
$diff_sec = intval($end_sec) - intval($start_sec);
$diff_usec = floatval($end_usec) - floatval($start_usec);
return floatval($diff_sec) + $diff_usec;
}
/**
* Sagacity encryption algorithm
*
* @param string $data
*
* @return string
*/
function my_encrypt($data) {
// Remove the base64 encoding from our key
$encryption_key = base64_decode(SALT);
// Generate an initialization vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(ALGORITHM));
// Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
$encrypted = openssl_encrypt($data, ALGORITHM, $encryption_key, 0, $iv);
// The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
return base64_encode($encrypted . '::' . $iv);
}
/**
* Sagacity decryption algorithm
*
* @param string $data
* @param string $key
*
* @return string
*/
function my_decrypt($data) {
// Remove the base64 encoding from our key
$encryption_key = base64_decode(SALT);
// To decrypt, split the encrypted data from our IV - our unique separator used was "::"
list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
return openssl_decrypt($encrypted_data, ALGORITHM, $encryption_key, 0, $iv);
}
/**
* Sagacity method to replace string by reference
*
* @param string $search
* @param string $replace
* @param string &$subject
*/
function my_str_replace($search, $replace, &$subject) {
$subject = str_replace($search, $replace, $subject);
}
/**
* Helper method to add DateIntervals
*
* @param DateInterval $i1
* @param DateInterval $i2
*
* @return DateInterval
*/
function add_intervals($i1, $i2) {
$a = new DateTime("00:00");
$b = clone $a;
if (is_a($i1, 'DateInterval')) {
$a->add($i1);
}
if (is_a($i2, 'DateInterval')) {
$a->add($i2);
}
return $b->diff($a);
}

148
inc/menu.inc Normal file
View File

@ -0,0 +1,148 @@
<?php
/**
* File: menu.inc
* Author: Ryan Prather
* Purpose: Display the top nav menu
* Created: Sep 11, 2013
*
* Portions Copyright 2016: Cyber Perspectives, All rights reserved
* Released under the Apache v2.0 License
*
* Portions Copyright (c) 2012-2015, Salient Federal Solutions
* Portions Copyright (c) 2008-2011, Science Applications International Corporation (SAIC)
* Released under Modified BSD License
*
* See license.txt for details
*
* Change Log:
* - Sep 11, 2013 - File created
* - Sep 1, 2016 - Copyright and whole menu updated
* - Oct 10, 2016 - Removed "Upload Progress" menu item because it's functionality was merged in withe results/index.php
* - Oct 24, 2016 - Renamed "Mission Systems" to "Systems"
* Renamed "Site" to "Sites"
* Added "Target Filter" to top nav
* - Mar 8, 2017 - Formatting and added check for presence of /proc directory before displaying Procedural Operations menu
* - Apr 5, 2017 - Moved montre function, CSS, and related content having to do with menu code
* - Jan 10, 2018 - Added link to /ste/stats.php
*/
$results = '';
$data = '';
$ops = '';
$report = '';
$script_name = filter_input(INPUT_SERVER, 'SCRIPT_NAME', FILTER_SANITIZE_STRING);
if (preg_match('/ste|proc/', $script_name)) {
$ops = " class='active'";
}
elseif (preg_match('/results/', $script_name)) {
$results = " class='active'";
}
elseif (preg_match('/data/', $script_name)) {
$data = " class='active'";
}
elseif (preg_match('/report/', $script_name)) {
$report = " class='active'";
}
?>
<script type='text/javascript'>
$(function () {
window.onload = montre;
});
function montre(id) {
$("dd[id^='smenu']").hide();
if (id && typeof id == 'string') {
$('#' + id).show();
}
}
</script>
<style type="text/css">
dl, dt, dd, ul, li {
margin: 0;
padding: 0;
list-style-type: none;
z-index: 100;
}
#menu {
width: 25px;
display: table-cell;
}
#menu dt {
cursor: pointer;
text-align: center;
font-weight: bold;
}
#menu dd {
position: fixed;
z-index: 100;
width: 10em;
background: #B4B2B2;
border: 1px solid gray;
}
#menu ul {
padding: 2px;
}
#menu li {
text-align: center;
font-size: 85%;
height: 18px;
line-height: 18px;
}
#menu li a, #menu dt a {
color: #000;
text-decoration: none;
display: block;
}
#menu li a:hover {
text-decoration: underline;
}
</style>
<ul id="menu-bar">
<li<?php print $ops; ?>><a href="javascript:void(0);">Operations</a>
<ul>
<li><a href="/ste">ST&amp;E Operations</a></li>
<li><a href='/ste/stats.php'>Stats</a></li>
<?php if (file_exists(DOC_ROOT . "/proc")) { ?>
<li><a href = "/proc">Procedural Operations</a></li>
<?php } ?>
</ul>
</li>
<li<?php print $results; ?>><a href="javascript:void(0);">Scans</a>
<ul>
<li><a href="/results">Results</a></li>
<li><a href="/results/?add_scan=1">Add Scan</a></li>
</ul>
</li>
<?php if (file_exists(DOC_ROOT . "/report")) { ?>
<li<?php print $report; ?>><a href="javascript:void(0);">Report</a>
<ul>
<li><a href="/report/sanity.php?step=1">Sanity Check</a></li>
<li><a href="/report/create.php">Create Risk Assessment</a></li>
</ul>
</li>
<?php } ?>
<li<?php print $data; ?>><a href="javascript:void(0);">Management</a>
<ul>
<li><a href="/data/?p=MSMgmt">Systems</a></li>
<li><a href="/data/?p=SiteMgmt">Sites</a></li>
<li><a href="/data/?p=STEMgmt">ST&amp;E</a></li>
<li><a href="/data/?p=CatMgmt">Catalog</a></li>
<li><a href="/data/?p=Settings">Settings</a></li>
<li><a href="/data/?p=Search">Search</a></li>
<li><a href="/data/?p=TgtSearch">Target Search</a></li>
</ul>
</li>
<li>
<form method="post" action="/data/?p=Search" target="_blank" style="display:inline-block;">
<input type="text" style="vertical-align:text-bottom;" name="q" placeholder="Search..." />
</form>
</li>
</ul>

336
inc/reference-filter.inc Normal file
View File

@ -0,0 +1,336 @@
<?php
/**
* File: reference-filter.php
* Author: Ryan
* Purpose: This file contains all that is necessary for the display of the reference filter.
* When filtering, the results go into a dive with the id='reference-filter-results', place wherever desired.
* Created: Sep 3, 2016
*
* Copyright 2016: Cyber Perspectives, All rights reserved
* Released under the Apache v2.0 License
*
* See license.txt for details
*
* Change Log:
* - Sep 3, 2016 - File created
* - Mar 4, 2017 - Changed AJAX to use /ajax.php instead of /cgi-bin/ajax.php
*/
include_once 'database.inc';
if (!$db) {
$db = new db();
}
$filters = $db->get_Filters('reference');
$col = 250;
$col2 = 398;
if (isset($reference_filter_width)) {
$reference_filter_width -= 40;
$col = floor($reference_filter_width / 5);
$col2 = $col * 2;
}
else {
$reference_filter_width = 1200;
}
?>
<script type='text/javascript'>
function save_reference_filter() {
if ($('#filter option').length < 1) {
alert("Please add a filter to save");
console.warn("No filters to save");
return;
}
else if ($('#filter-name').val() == '') {
alert("Please type a filter name");
console.warn("No filter name");
return;
}
var criteria = '';
$('#filter option').each(function () {
criteria += $(this).text() + "\n";
});
$.post(
'/ajax.php',
{
'action': 'save_filter',
'criteria': criteria,
'type': 'reference',
'name': $('#filter-name').val(),
},
save_filter_result,
'text'
);
}
function save_filter_result(data) {
if (data == 'false') {
alert('Filter saving failed');
}
}
function add_filter() {
if ($('#filter-options').val() == '0') {
alert('Must select a filter option');
return;
}
var op = ' = ';
var op_str = ' IS ';
if ($('#not').is(':checked') && $('#like').is(':checked')) {
op = ' !~ ';
op_str = ' NOT LIKE ';
}
else if ($('#not').is(':checked')) {
op = ' != ';
op_str = ' NOT EQUAL ';
}
else if ($('#like').is(':checked')) {
op = ' ~= ';
op_str = ' LIKE ';
}
var filter = '';
switch ($('#filter-options').val()) {
default:
filter = $('#filter-text').val();
}
$('#filter').append($('<option>', {
text: $('#filter-options option:selected').text() +
op + '\'' + filter + '\'',
title: $('#filter-options option:selected').text() +
op_str + '\'' + filter + '\''
}));
filter_clean_up();
}
if (typeof window.collapse_expand_data === 'undefined') {
window.collapse_expand_data = function (selection) {
if ($('#' + selection + '-img').attr('src') == '/img/right-arrow.png') {
$('#' + selection + '-img').attr('src', '/img/down-arrow.png');
}
else {
$('#' + selection + '-img').attr('src', '/img/right-arrow.png');
}
$('#' + selection).toggle(300);
};
}
function execute_filter() {
if ($('#filter option').length == 0) {
alert('Please add something to filter');
console.error('Nothing to filter');
return;
}
var criteria = '';
$('#filter option').each(function () {
criteria += $(this).text() + "\n";
});
$.post(
'/ajax.php',
{
action: 'reference-filter',
'criteria': criteria,
start_count: $('#filter-start').val(),
count: $('#filter-count').val(),
},
display_reference_filter_results,
'html'
);
}
function display_reference_filter_results(data) {
if ($('#reference-filter-results').length == 0) {
console.error("Cannot find div to populate targets in");
return;
}
var odd = true;
$('#reference-filter-results').html("");
//$('#filter-start').val(parseInt($('#filter-start').val())+parseInt($('#filter-count').val()));
$(data).find("reference").each(function () {
odd = !odd;
});
}
function retrieve_saved_filter() {
$('#filter-start').val(0);
$.post(
'/ajax.php',
{
action: 'get-saved-filter',
'type': 'reference',
name: $('#saved-filter').val(),
},
display_saved_filter,
'text'
);
}
function display_saved_filter(data) {
$('#filter').html(data);
}
function change_filter_option() {
$('.filter').hide();
switch ($('#filter-options').val()) {
default:
$('#filter-text').show();
}
}
function filter_clean_up() {
$('#filter-start').val(0);
$('#filter-options').val(0);
$('#filter-text').val('');
$('#sw-filter').val('');
$('#like').attr('checked', false);
$('#not').attr('checked', false);
$('#sw-filter,#availableSoftware,.filter').hide();
$('#filter-text,#filter').show();
}
</script>
<style type='text/css'>
.title {
width: <?php print $reference_filter_width; ?>px;
background-color: #808080;
font-size: 14pt;
font-weight: bolder;
text-decoration: italic;
text-align: left;
padding-left: 20px;
color: black;
margin-top: 5px;
border: solid 1px black;
}
.col {
width: <?php print $col - 10; ?>px;
margin: 5px;
height: 108px;
display: inline-block;
vertical-align: top;
}
.col2 {
width: <?php print $col2 - 10; ?>px;
margin: 5px;
height: 108px;
display: inline-block;
vertical-align: top;
}
#load-more {
width: 100%;
text-align: center;
background-color: #808080;
display: none;
}
#load-more a {
color: #fff;
font-size: 18px;
text-decoration: none;
}
.checklist_image {
width: 32px;
vertical-align: middle;
}
.table-header {
width: <?php print($reference_filter_width += 22); ?>px;
text-align: center;
}
</style>
<div class='title'>
<img id='cat-filter-img' src='/img/right-arrow.png' onclick="javascript:collapse_expand_data('cat-filter');" style='width:20px;' />
&nbsp;&nbsp;Reference Filter...
</div>
<div id='cat-filter' style='display:none;'>
<input type='hidden' id='filter-start' value='0' />
<div class='col'>
<select id='filter-options' onchange="javascript:change_filter_option();" style='width:175px;'>
<option value='0'>Filter options...</option>
<option value='cce'>CCE</option>
<!-- <option value='cpe'>CPE</option> -->
<option value='cve'>CVE</option>
<option value='ctrl'>IA Control</option>
<option value='iavm'>IAVM</option>
<option value='nessus'>Nessus Plugin ID</option>
<option value='oval'>Oval</option>
<option value='ref'>Reference</option>
<option value='stig'>STIG ID</option>
<option value='sv_rule'>SV Rule</option>
<option value='vms'>VMS ID</option>
<option value='adv'>Vendor Advisory</option>
<option value='cc'>Check Contents</option>
<option value='st'>Short Title</option>
<option value='desc'>Description</option>
</select><br />
<input type='text' class='filter' id='filter-text' placeholder='Filter...' /><br />
<label for='not'>Not?</label>
<input type='checkbox' id='not' value='1' />
<label for='like'>Like?</label>
<input type='checkbox' id='like' value='1' />
<input type='button' id='add' value='Add' onclick="javascript:add_filter();" />
</div>
<div class='col2'>
<select name='filter[]' id='filter' multiple size='4' style="width:<?php print $col2 - 15; ?>px;height:110px;" title="Double-click to remove filter" ondblclick="$('#filter option:selected').remove();">
</select>
</div>
<div class='col' style='text-align: center;'>
<select id='filter-count'>
<option value='0'>Filter Count</option>
<option value='5'>5</option>
<option value='10'>10</option>
<option value='25'>25</option>
<option value='50'>50</option>
<option value='100'>100</option>
<option value='all'>All</option>
</select><br />
<input type='button' name='run-filter' value='Filter...' onclick="javascript:execute_filter();" />
</div>
<div class='col' style='text-align: right;'>
<select name='saved-filter' id='saved-filter' onchange="javascript:retrieve_saved_filter();">
<option value='0'>Saved Filters...</option>
<?php
foreach ($filters as $filter) {
print "<option>" . $filter['name'] . "</option>";
}
?>
</select><br />
<input type='text' name='filter-name' id='filter-name' /><br />
<input type='button' name='save-filter' value='Save Filter' onclick="javascript:save_reference_filter();" />
</div>
</div>
<div id='reference-filter-results'></div>
<div id='load-more'>
<a href='javascript:void(0);' onclick='load_more = true;execute_filter();'>Load More...</a>
</div>

312
inc/scan-filter.inc Normal file
View File

@ -0,0 +1,312 @@
<?php
/**
* File: scan-filter.php
* Author: Ryan
* Purpose: This file contains all that is necessary for the display of the scan filter.
* When filtering, the results go into a dive with the id='scan-filter-results', place wherever desired.
* Created: Sep 3, 2016
*
* Copyright 2016: Cyber Perspectives, All rights reserved
* Released under the Apache v2.0 License
*
* See license.txt for details
*
* Change Log:
* - Sep 3, 2016 - File created
* - Mar 4, 2017 - Changed AJAX to use /ajax.php instead of /cgi-bin/ajax.php
*/
include_once 'database.inc';
if (!$db) {
$db = new db();
}
$filters = $db->get_Filters('scan');
$col = 250;
$col2 = 398;
if (isset($scan_filter_width)) {
$scan_filter_width -= 40;
$col = floor($scan_filter_width / 5);
$col2 = $col * 2;
}
else {
$scan_filter_width = 1200;
}
$stes = $db->get_STE_List();
?>
<script type='text/javascript'>
function save_scan_filter() {
var criteria = '';
$('#filter option').each(function () {
criteria += $(this).text() + "\n";
});
$.post(
'/ajax.php',
{
'action': 'save_filter',
'criteria': criteria,
'type': 'scan',
'name': $('#filter-name').val(),
},
save_filter_result,
'text'
);
}
function save_filter_result(data) {
if (data == 'false') {
alert('Filter saving failed');
}
}
function add_filter() {
if ($('#filter-options').val() == '0') {
alert('Must select a filter option');
return;
}
var op = ' = ';
var op_str = ' IS ';
if ($('#not').is(':checked') && $('#like').is(':checked')) {
op = ' !~ ';
op_str = ' NOT LIKE ';
}
else if ($('#not').is(':checked')) {
op = ' != ';
op_str = ' NOT EQUAL ';
}
else if ($('#like').is(':checked')) {
op = ' ~= ';
op_str = ' LIKE ';
}
var filter = '';
switch ($('#filter-options').val()) {
default:
filter = $('#filter-text').val();
}
$('#filter').append($('<option>', {
text: $('#filter-options option:selected').text() +
op + '\'' + filter + '\'',
title: $('#filter-options option:selected').text() +
op_str + '\'' + filter + '\''
}));
filter_clean_up();
}
if (typeof window.collapse_expand_data === 'undefined') {
window.collapse_expand_data = function (selection) {
if ($('#' + selection + '-img').attr('src') == '/img/right-arrow.png') {
$('#' + selection + '-img').attr('src', '/img/down-arrow.png');
}
else {
$('#' + selection + '-img').attr('src', '/img/right-arrow.png');
}
$('#' + selection).toggle(300);
};
}
function execute_filter() {
if ($('#filter option').length == 0) {
alert('Please add something to filter');
console.error('Nothing to filter');
return;
}
var criteria = '';
$('#filter option').each(function () {
criteria += $(this).text() + "\n";
});
$.post(
'/ajax.php',
{
action: 'scan-filter',
'criteria': criteria,
start_count: $('#filter-start').val(),
count: $('#filter-count').val(),
},
display_scan_filter_results,
'html'
);
}
function display_scan_filter_results(data) {
if ($('#scan-filter-results').length == 0) {
console.error("Cannot find div to populate targets in");
return;
}
var odd = true;
$('#scan-filter-results').html("");
//$('#filter-start').val(parseInt($('#filter-start').val())+parseInt($('#filter-count').val()));
$(data).find("scan").each(function () {
odd = !odd;
});
}
function retrieve_saved_filter() {
$('#filter-start').val(0);
$.post(
'/ajax.php',
{
action: 'get-saved-filter',
'type': 'scan',
name: $('#saved-filter').val(),
},
display_saved_filter,
'text'
);
}
function display_saved_filter(data) {
$('#filter').html(data);
}
function change_filter_option() {
$('.filter').hide();
switch ($('#filter-options').val()) {
default:
$('#filter-text').show();
}
}
function filter_clean_up() {
$('#filter-start').val(0);
$('#filter-options').val(0);
$('#filter-text').val('');
$('#sw-filter').val('');
$('#like').attr('checked', false);
$('#not').attr('checked', false);
$('#sw-filter,#availableSoftware,.filter').hide();
$('#filter-text,#filter').show();
}
</script>
<style type='text/css'>
.title {
width: <?php print $scan_filter_width; ?>px;
background-color: #808080;
font-size: 14pt;
font-weight: bolder;
text-decoration: italic;
text-align: left;
padding-left: 20px;
color: black;
margin-top: 5px;
border: solid 1px black;
}
.col {
width: <?php print $col - 10; ?>px;
margin: 5px;
height: 108px;
display: inline-block;
vertical-align: top;
}
.col2 {
width: <?php print $col2 - 10; ?>px;
margin: 5px;
height: 108px;
display: inline-block;
vertical-align: top;
}
#load-more {
width: 100%;
text-align: center;
background-color: #808080;
display: none;
}
#load-more a {
color: #fff;
font-size: 18px;
text-decoration: none;
}
.table-header {
width: <?php print($scan_filter_width += 22); ?>px;
text-align: center;
}
</style>
<div class='title'>
<img id='cat-filter-img' src='/img/right-arrow.png' onclick="javascript:collapse_expand_data('cat-filter');" style='width:20px;' />
&nbsp;&nbsp;Scan Filter...
<select name='ste' id='ste'>
<?php print $stes; ?>
</select>
</div>
<div id='cat-filter' style='display:none;'>
<input type='hidden' id='filter-start' value='0' />
<div class='col'>
<select id='filter-options' onchange="javascript:change_filter_option();" style='width:175px;'>
<option value='0'>Filter options...</option>
<option value='src'>Source</option>
<option value='target'>Target</option>
<option value='count_eq'>Finding Count equal</option>
<option value='count_gt'>Finding Count greater than</option>
<option value='count_lt'>Finding Count less than</option>
<option value='cpe'>CPE</option>
</select><br />
<input type='text' class='filter' id='filter-text' placeholder='Filter...' /><br />
<label for='not'>Not?</label>
<input type='checkbox' id='not' value='1' />
<label for='like'>Like?</label>
<input type='checkbox' id='like' value='1' />
<input type='button' id='add' value='Add' onclick="javascript:add_filter();" />
</div>
<div class='col2'>
<select name='filter[]' id='filter' multiple size='4' style="width:<?php print $col2 - 15; ?>px;height:110px;" title="Double-click to remove filter" ondblclick="$('#filter option:selected').remove();">
</select>
</div>
<div class='col' style='text-align: center;'>
<select id='filter-count'>
<option value='0'>Filter Count</option>
<option value='5'>5</option>
<option value='10'>10</option>
<option value='25'>25</option>
<option value='50'>50</option>
<option value='100'>100</option>
<option value='all'>All</option>
</select><br />
<input type='button' name='run-filter' value='Filter...' onclick="javascript:execute_filter();" />
</div>
<div class='col' style='text-align: right;'>
<select name='saved-filter' id='saved-filter' onchange="javascript:retrieve_saved_filter();">
<option value='0'>Saved Filters...</option>
<?php
foreach ($filters as $filter) {
print "<option>" . $filter['name'] . "</option>";
}
?>
</select><br />
<input type='text' name='filter-name' id='filter-name' /><br />
<input type='button' name='save-filter' value='Save Filter' onclick="javascript:save_scan_filter();" />
</div>
</div>

607
inc/target-filter.inc Normal file
View File

@ -0,0 +1,607 @@
<?php
/**
* File: target-filter.inc
* Author: Ryan
* Purpose: This file contains all that is necessary for the display of the target filter.
* When filtering, the results go into a dive with the id='target-filter-results', place wherever desired.
* Created: Aug 21, 2016
*
* Copyright 2016: Cyber Perspectives, All rights reserved
* Released under the Apache v2.0 License
*
* See license.txt for details
*
* Change Log:
* - Aug 21, 2016 - File created
* - Oct 10, 2016 - Converted AJAX code to retrieve JSON from request instead of XML (bug #5)
* - Jan 30, 2017 - Formatting
* - Mar 4, 2017 - Changed AJAX to use /ajax.php instead of /cgi-bin/ajax.php
* - Mar 13, 2017 - Added support for notice when no targets found or timeouts and increased timeout to 60 seconds
*/
include_once 'database.inc';
if (!$db) {
$db = new db();
}
$filters = $db->get_Filters('target');
$col = 250;
$col2 = 398;
if (isset($target_filter_width)) {
$target_filter_width -= 40;
$col = floor($target_filter_width / 5);
$col2 = $col * 2;
} else {
$target_filter_width = 1200;
}
$stes = $db->get_STE_List();
if (!$ste_id) {
$ste_id = filter_input(INPUT_POST, 'ste', FILTER_VALIDATE_INT);
if (!$ste_id) {
$ste_id = filter_input(INPUT_COOKIE, 'ste', FILTER_VALIDATE_INT);
}
}
?>
<script src="/script/skinable_tabs.min.js" type='text/javascript'></script>
<script type='text/javascript'>
var load_more = false;
/**
* save_target_filter function
* Function built to perform AJAX query of the database and save the filter to the database.
*/
function save_target_filter() {
if ($('#filter option').length < 1) {
alert("Please add a filter to save");
console.warn("No filters to save");
return;
} else if ($('#filter-name').val() === '') {
alert("Please type a filter name");
console.warn("No filter name");
return;
}
var criteria = '';
$('#filter option').each(function () {
if ($(this).text())
criteria += $(this).text() + "\n";
});
$.ajax('/ajax.php', {
data: {
'action': 'save_filter',
'criteria': criteria,
'type': 'target',
'name': $('#filter-name').val()
},
success: save_filter_result,
dataType: 'text',
method: 'post'
});
}
/**
* save_filter_result
* This function is built to perform whatever actions are required on the returned AJAX data
*
* @param data
* The data returned from the AJAX query
*/
function save_filter_result(data) {
if (data === 'false') {
alert('Filter saving failed');
}
}
/**
* add_filter function
* This function adds a selected filter and the data to the select box for querying
*/
function add_filter() {
if ($('#filter-options').val() === '0') {
alert('Must select a filter option');
console.error("No filter option selected");
return;
}
var op = ' = ';
var op_str = ' IS ';
if ($('#not').is(':checked') && $('#like').is(':checked')) {
op = ' !~ ';
op_str = ' NOT LIKE ';
} else if ($('#not').is(':checked')) {
op = ' != ';
op_str = ' NOT EQUAL ';
} else if ($('#like').is(':checked')) {
op = ' ~= ';
op_str = ' LIKE ';
}
var filter = '';
switch ($('#filter-options').val()) {
case 'cat':
filter = $('#cats').val();
break;
case 'auto_status':
case 'man_status':
case 'data_status':
case 'fp_cat1_status':
filter = $('#task-status').val();
break;
case 'os':
case 'sw':
filter = $('#sw-filter').val();
break;
default:
filter = $('#filter-text').val();
}
$('#filter').append($('<option>', {
text: $('#filter-options option:selected').text() +
op + '\'' + filter + '\'',
title: $('#filter-options option:selected').text() +
op_str + '\'' + filter + '\''
}));
filter_clean_up();
}
/**
* First check and see if there is a function already declared called collapse_expand_data
* If the function is not delcared in the document then declare and set it.
*/
if (typeof window.collapse_expand_data === 'undefined') {
window.collapse_expand_data = function (selection) {
if ($('#' + selection + '-img').attr('src') === '/img/right-arrow.png') {
$('#' + selection + '-img').attr('src', '/img/down-arrow.png');
} else {
$('#' + selection + '-img').attr('src', '/img/right-arrow.png');
}
$('#' + selection).toggle(300);
};
}
/**
* execut_filter function
* This function performs a AJAX query to execute the filter and retrieve the applicable data.
*/
function execute_filter() {
if ($('#filter option').length === 0) {
alert('Please add something to filter');
console.error('Nothing to filter');
return;
}
if ($('#ste').val() === 0) {
alert("Please select an ST&E");
console.error("No ST&E selected");
return;
}
var criteria = '';
$('#filter option').each(function () {
if ($(this).text())
criteria += $(this).text() + "\n";
});
$.ajax('/ajax.php', {
data: {
action: 'target-filter',
ste: $('#ste').val(),
'criteria': criteria,
start_count: $('#filter-start').val(),
count: $('#filter-count').val()
},
success: display_target_filter_results,
error: function (xhr, status, error) {
if (status === 'timeout') {
alert("Request timed out");
} else {
console.error(error);
}
},
dataType: 'json',
timeout: 60000,
method: 'post'
});
}
/**
* display_target_filter_results function
* This function displays the data that was retrieved from the AJAX query (execute_filter)
*
* @param data
* Returned AJAX data
*/
function display_target_filter_results(data) {
if ($('#target-filter-results').length === 0) {
console.error("Cannot find place to populate targets");
}
if (data.error) {
alert(data.error);
return;
}
if (data.count < 1) {
$('#target-filter-results').html('No targets found');
return;
}
var odd = true;
if (!load_more) {
$('#target-filter-results').html("");
}
if ($('#filter-count').val() !== 'all') {
$('#filter-start').val(parseInt($('#filter-start').val()) + parseInt($('#filter-count').val()));
} else {
$('#filter-start').val(0);
}
for (var x in data.targets) {
$('#target-filter-results').append(
"<div class='" + (odd ? "odd_row" : "even_row") + " cat_" + data.targets[x].cat_id + "' style='border:none;'>" +
"<span class='cat-cell' style='width:150px;text-align:left'>" +
"<input type='checkbox' value='" + data.targets[x].id + "' onclick='javascript:update_tgt_chk(this);' />" +
"<a href='target.php?ste=" + data.targets[x].ste_id + "&tgt=" + data.targets[x].id + "' class='host'>" + data.targets[x].name + "</a><br />" +
"<img src='/img/notes.png' style='width:24px;' onclick='get_notes(" + data.targets[x].id + "); ' />" +
"</span>" +
"<span class='cat-cell' style='width:150px;line-height:1.25em;'>" + data.targets[x].os + "</span>" +
"<span class='cat-cell' style='width:145px;'>" +
(data.targets[x].scans ? data.targets[x].scans : "&nbsp;") +
"</span>" +
"<span class='cat-cell' style='width:145px;'>" +
(data.targets[x].chk ? data.targets[x].chk : "&nbsp;") +
"</span>" +
"</div>"
);
odd = !odd;
}
if ($('#filter-count').val() !== 'all') {
if (data.count === $('#target-filter-results').find('div').length) {
$('#load-more').hide();
} else if (data.count > $('#filter-count').val()) {
$('#load-more').show();
}
}
load_more = false;
}
/**
* retrieve_saved_filter function
* This function performs an AJAX query to retrieves the selected saved query from the database for display
*/
function retrieve_saved_filter() {
$('#filter-start').val(0);
$('#filter option').remove();
$.ajax('/ajax.php', {
data: {
action: 'get-saved-filter',
'type': 'target',
name: $('#saved-filter').val()
},
success: function (data) {
for (var x in data) {
if (data[x]) {
$('#filter').append("<option>" + data[x] + "</option>");
}
}
execute_filter();
},
dataType: 'json',
timeout: 1000,
method: 'post'
});
}
/**
* change_filter_option function
* This function changes the display for specific filter types.
*/
function change_filter_option() {
$('.filter').hide();
switch ($('#filter-options').val()) {
case 'cat':
$('#cats').show();
break;
case 'auto_status':
case 'man_status':
case 'data_status':
case 'fp_cat1_status':
$('#task-status').show();
break;
case 'os':
case 'sw':
$('#sw-filter').show();
break;
default:
$('#filter-text').show();
}
}
/**
* filter_software function
* This function performs an AJAX query to retrive possible CPE matches from the typed filter (inc OS)
*/
function filter_software() {
var action = 'sw_filter';
if ($('#filter-options').val() === 'os') {
action = 'os_filter';
}
if ($('#sw-filter').val().length < 3) {
return;
}
$.ajax('/ajax.php', {
data: {
'action': action,
tgt_id: '<?php print isset($_REQUEST['tgt']) ? $_REQUEST['tgt'] : '' ?>',
filter: $('#sw-filter').val()
},
success: function (data) {
$('#availableSoftware div').remove();
for (var x in data) {
$('#availableSoftware').append("<div sw_id='" + data[x].sw_id + "' cpe='" + data[x].cpe + "'>" + data[x].sw_string + "</div>");
}
$('#availableSoftware div').each(function () {
$(this).on({
mouseover: function () {
$(this).addClass("swmouseover");
},
mouseout: function () {
$(this).removeClass("swmouseover");
},
click: function () {
if ($('#filter-options').val() === 'os') {
$('#filter').append("<option title=\"os IS '" + $(this).attr('cpe') + "'\">OS = '" + $(this).attr('cpe') + "'</option>");
} else {
$('#filter').append("<option title=\"sw IS '" + $(this).attr('cpe') + "'\">SW = '" + $(this).attr('cpe') + "'</option>");
}
$('#availableSoftware').children().remove();
$('#availableSoftware').hide();
$('#filter').show();
filter_clean_up();
}});
});
$('#availableSoftware').show();
$('#filter').hide();
},
dataType: 'json',
method: 'post',
timeout: 10000
});
}
/**
* filter_clean_up function
* This function was built to clean up the filter and reset it.
*/
function filter_clean_up() {
$('#filter-start').val(0);
$('#filter-options').val(0);
$('#filter-text').val('');
$('#sw-filter').val('');
$('#like').attr('checked', false);
$('#not').attr('checked', false);
$('#sw-filter,#availableSoftware,.filter').hide();
$('#filter-text,#filter').show();
}
/**
* Function to get the notes for the target
*
* @param tgt_id
*/
function get_notes(tgt_id) {
$.ajax('/ajax.php', {
data: {
action: 'get-target-notes',
'tgt-id': tgt_id
},
beforeSend: function () {
},
success: function (data) {
$('#notes').html(data.notes);
},
error: function (xhr, status, error) {
console.error(error);
},
complete: function () {
},
dataType: 'json',
method: 'post',
timeout: 1000
});
}
</script>
<style type='text/css'>
.title {
width: 1178px;
background-color: #808080;
font-size: 14pt;
font-weight: bolder;
font-style: italic;
text-align: left;
padding-left: 20px;
color: black;
margin-top: 5px;
border: solid 1px black;
}
#load-more {
width: 100%;
text-align: center;
background-color: #808080;
display: none;
}
#load-more a {
color: #fff;
font-size: 18px;
text-decoration: none;
}
.checklist_image {
width: 32px;
vertical-align: middle;
}
.col {
width: <?php print $col - 10; ?>px;
margin: 5px;
height: 108px;
display: inline-block;
vertical-align: top;
}
.col2 {
width: <?php print $col2 - 10; ?>px;
margin: 5px;
height: 108px;
display: inline-block;
vertical-align: top;
}
.swmouseover {
background-color: #1D57A0;
color: #fff;
cursor: pointer;
}
.header {
background-color: #31363C;
color: #fff;
display: table-cell;
}
.table-header {
width: <?php print($target_filter_width += 22); ?>px;
text-align: center;
}
.header-col {
width: 49%;
display: inline-block;
}
</style>
<div class='title'>
<div class='header-col'>
<img id='cat-filter-img' src='/img/right-arrow.png' onclick="javascript:collapse_expand_data('cat-filter');" style='width: 20px;' />
&nbsp;&nbsp;Target Filter...
<select name='ste' id='ste'>
<?php print $stes; ?>
</select>
</div>
<div class='header-col'>
<select name='saved-filter' id='saved-filter' onchange="retrieve_saved_filter();">
<option value='0'>Saved Filters...</option>
<?php
foreach ($filters as $filter) {
print "<option>" . $filter['name'] . "</option>";
}
?>
</select>
<input type='text' name='filter-name' id='filter-name' />
<input type='button' name='save-filter' value='Save Filter' onclick="javascript:save_target_filter();" />
</div>
</div>
<div id='cat-filter' style='display: none;'>
<input type='hidden' id='filter-start' value='0' />
<div class='col'>
<select id='filter-options' onchange="javascript:change_filter_option();" style='width:175px;'>
<option value='0'>Filter options...</option>
<option value='cat'>Category</option>
<option value='name'>Name</option>
<option value='os'>OS</option>
<option value='sw'>Installed Software</option>
<option value='auto_status'>Auto Status</option>
<option value='man_status'>Manual Status</option>
<option value='data_status'>Data Gathering Status</option>
<option value='fp_cat1_status'>FP/Cat I Status</option>
<option value='open_port' title='tcp/{port #} or udp/{port #}'>Open Port</option>
</select><br />
<select id='cats' class='filter' style='display: none;'>
<?php
if (isset($ste)) {
$cats = $db->get_STE_Cat_List($ste);
foreach ($cats as $cat) {
print "<option value='" . $cat->get_ID() . "'>" . $cat->get_Name() . "</option>";
}
}
?>
</select>
<select id='task-status' class='filter' style='display: none;'>
<?php
$task_status = $db->get_Task_Statuses();
foreach ($task_status as $id => $status) {
print "<option>$status</option>";
}
?>
</select>
<input type='text' id='sw-filter' class='filter' placeholder='CPE...' style='display: none;' onkeyup="javascript:filter_software();" autocomplete="off" />
<input type='text' class='filter' id='filter-text' placeholder='Filter...' /><br />
<label for='not'>Not?</label>
<input type='checkbox' id='not' value='1' />
<label for='like'>Like?</label>
<input type='checkbox' id='like' value='1' />
<input type='button' id='add' value='Add' onclick="javascript:add_filter();" />
</div>
<div class='col2'>
<div id='availableSoftware' class='filter-results' style='z-index: 1000; display: none; overflow-x: scroll; height: 110px;'></div>
<select name='filter[]' id='filter' multiple size='4' style="width:<?php print $col2 - 15; ?>px;height:110px;" title="Double-click to remove filter" ondblclick="$('#filter-start').val(0); $('#filter option:selected').remove();">
</select>
</div>
<div class='col' style='text-align: center;'>
<select id='filter-count'>
<option value='all'>All</option>
<option value='5'>5</option>
<option value='10'>10</option>
<option value='25'>25</option>
<option value='50'>50</option>
<option value='100'>100</option>
</select><br /> <input type='button' name='run-filter'
value='Filter...' onclick="javascript:execute_filter();" />
</div>
</div>
<div id='target-header'>
<div class='table-header' style='border: 0;'>
<span class='header' style='width:<?php print ($target_filter_width * 0.125); ?>px;text-align:left'>Name</span>
<span class='header' style='width:<?php print ($target_filter_width * 0.125); ?>px;line-height:1.25em;'>OS</span>
<span class='header' style='width:<?php print ($target_filter_width * 0.12083); ?>px;'>Scans</span>
<span class='header' style='width:<?php print ($target_filter_width * 0.12083); ?>px;'>Checklists</span>
<span class='header' style='width:<?php print ($target_filter_width * 0.05); ?>px;'>I</span>
<span class='header' style='width:<?php print ($target_filter_width * 0.05); ?>px;'>II</span>
<span class='header' style='width:<?php print ($target_filter_width * 0.05); ?>px;'>III</span>
<span class='header' style='width:<?php print ($target_filter_width * 0.05); ?>px;'>NF</span>
<span class='header' style='width:<?php print ($target_filter_width * 0.05); ?>px;'>NA</span>
<span class='header' style='width:<?php print ($target_filter_width * 0.05); ?>px;'>NR</span>
<span class='header' style='width:<?php print ($target_filter_width * 0.05); ?>px;'>C</span>
<span class='header' style='width:<?php print ($target_filter_width * 0.05); ?>px;'>A</span>
</div>
</div>
<div id='target-filter-results'></div>
<div id='load-more'>
<a href='javascript:void(0);'
onclick='load_more = true;execute_filter();'>Load More...</a>
</div>

264
inc/validation.inc Normal file
View File

@ -0,0 +1,264 @@
<?php
/**
* File: validation.inc
* Author: Ryan Prather
* Purpose: This class will contain all the validation routines that are necessary for the program
* Created: Oct 14, 2013
*
* Portions Copyright 2016-2017: Cyber Perspectives, LLC, All rights reserved
* Released under the Apache v2.0 License
*
* Portions Copyright (c) 2012-2015, Salient Federal Solutions
* Portions Copyright (c) 2008-2011, Science Applications International Corporation (SAIC)
* Released under Modified BSD License
*
* See license.txt for details
*
* Change Log:
* - Oct 14, 2013 - File created
* - Jun 5, 2017 - Removed unnecessary elements and added validation for IPv4 addresses
*/
/**
*
* @author Ryan Prather
*
*/
class validation {
/**
* Constant regular expression to validate MySQL formated dates
*
* @var string
*/
const DATE_FORMAT = "/Y\-m\-d/";
/**
* Constant regular expression to validate MySQL formatted time
*
* @var string
*/
const TIME_FORMAT = "/H\:i\:s/";
/**
* Constant regular expression to validate MySQL formatted DateTime
*
* @var string
*/
const DATETIME_FORMAT = "/Y\-m\-d H\:i\:s/";
/**
* Constant regular expression to validate US phone numbers
*
* @var string
*/
const PHONE_FORMAT = "/\(?\d{3}\)?[\ \-]\d{3}\-\d{4}/";
/**
*
* @param string $string
* The string to evaluate
* @param int $min [optional]
* The minimum length of string allowed
* @param int $max [optional]
* The maximum length of string allowed
*
* @return boolean
* Returns TRUE if a valid string (and within min and max length), otherwise FALSE
*/
public static function valid_string($string, $min = null, $max = null) {
if (!is_string($string)) {
return false;
}
if (!is_null($min) && strlen($string) < $min) {
return false;
}
if (!is_null($max) && strlen($string) > $max) {
return false;
}
return true;
}
/**
* Function to validate an integer
*
* @param int $int
* The integer to evaluate
* @param int $min [optional]
* The minimum value allowed
* @param int $max [optional]
* The maximum value allowed
*
* @return boolean
* Returns TRUE if a valid integer (and within min and max if passed), otherwise FALSE
*/
public static function valid_int($int, $min = null, $max = null) {
if (!is_int($int)) {
return false;
}
if ($min > 0 && $int < $min) {
return false;
}
if ($max > 0 && $int > $max) {
return false;
}
return true;
}
/**
* Function to validate a date/time/datetime value
*
* @param DateTime|string $date
* The date to evaluate (can be a DateTime object or string that the DateTime class can parse without special instructions)
* @param DateTime $min [optional]
* The minimum date allowed
* @param DateTime $max [optional]
* The maximum date allowed
*
* @return boolean
* Returns TRUE if it is a valid date and within optional min and max values
*/
public static function valid_date($date, $min = null, $max = null) {
try {
$dt = new DateTime($date);
}
catch (Exception $ex) {
return false;
}
if (!is_null($min) && is_a($min, 'DateTime') && $dt < $min) {
return false;
}
if (!is_null($max) && is_a($max, 'DateTime') && $dt > $max) {
return false;
}
return true;
}
/**
* Function to validate a floating point number
*
* @param float $float
* The value to evaluate
* @param float $min [optional]
* The minimum value for the float
* @param float $max [optional]
* The maximum value for the float
*
* @return boolean
* Returns TRUE if it is a valid float value (within min and max if passed)
*/
public static function valid_float($float, $min = null, $max = null) {
if (!is_float($float)) {
return false;
}
if ($min > 0.0 && $float < $min) {
return false;
}
if ($max > 0.0 && $float > $max) {
return false;
}
return true;
}
/**
* Function to validate an email address
*
* @param string $email
* Email address to evaluate
*
* @return boolean
* Returns TRUE if valid email address, otherwise FALSE
*/
public static function valid_email($email) {
$atIndex = strrpos($email, "@");
if (is_bool($atIndex) && !$atIndex) {
return false;
}
else {
$domain = substr($email, $atIndex + 1);
$local = substr($email, 0, $atIndex);
$localLen = strlen($local);
$domainLen = strlen($domain);
if ($localLen < 1 || $localLen > 64) {
// local part length exceeded
return false;
}
else if ($domainLen < 1 || $domainLen > 255) {
// domain part length exceeded
return false;
}
else if ($local[0] == '.' || $local[$localLen - 1] == '.') {
// local part starts or ends with '.'
return false;
}
else if (preg_match('/\\.\\./', $local)) {
// local part has two consecutive dots
return false;
}
else if (!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)) {
// character not valid in domain part
return false;
}
else if (preg_match('/\\.\\./', $domain)) {
// domain part has two consecutive dots
return false;
}
else if (!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\", "", $local))) {
// character not valid in local part unless
// local part is quoted
if (!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\", "", $local))) {
return false;
}
}
if (!(checkdnsrr($domain, "MX") || checkdnsrr($domain, "A"))) {
// domain not found in DNS
return false;
}
}
return true;
}
/**
* Function to validate a North American phone number
*
* @param string $phone
* The phone number to evaluate
*
* @return boolean
* Return TRUE if it is a valid US/CA phone number
*/
public static function valid_phone($phone) {
if (!preg_match(self::PHONE_FORMAT, $phone)) {
return false;
}
return true;
}
/**
* Function to make sure that an IP is valid
*
* @param string $ip
* The IPv4 address to evaluate
*
* @return boolean
* Returns TRUE if it is a valid IPv4 address, otherwise FALSE
*/
public static function valid_ip($ip) {
if (!preg_match("/((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))|(^\s*((?=.{1,255}$)(?=.*[A-Za-z].*)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*)\s*$)/", $ip)) {
return false;
}
return true;
}
}

7
inc/vendor/autoload.php vendored Normal file
View File

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

View File

@ -0,0 +1,5 @@
/build/
/vendor/
/composer.lock
/phpunit.xml
/tests/fixtures/*.log

View File

@ -0,0 +1,162 @@
tools:
external_code_coverage: true
php_sim: true
php_pdepend: true
php_analyzer: true
filter:
excluded_paths:
- 'tests/*'
- 'examples/*'
checks:
php:
verify_property_names: true
verify_argument_usable_as_reference: true
verify_access_scope_valid: true
variable_existence: true
useless_calls: true
use_statement_alias_conflict: true
use_self_instead_of_fqcn: true
uppercase_constants: true
unused_variables: true
unused_properties: true
unused_parameters: true
unused_methods: true
unreachable_code: true
too_many_arguments: true
symfony_request_injection: true
switch_fallthrough_commented: true
sql_injection_vulnerabilities: true
spacing_of_function_arguments: true
spacing_around_non_conditional_operators: true
spacing_around_conditional_operators: true
space_after_cast: true
single_namespace_per_use: true
simplify_boolean_return: true
side_effects_or_types: true
security_vulnerabilities: true
scope_indentation:
spaces_per_level: '4'
return_doc_comments: true
return_doc_comment_if_not_inferrable: true
require_scope_for_properties: true
require_scope_for_methods: true
require_php_tag_first: true
require_braces_around_control_structures: true
remove_trailing_whitespace: true
remove_php_closing_tag: true
remove_extra_empty_lines: true
psr2_switch_declaration: true
psr2_control_structure_declaration: true
psr2_class_declaration: true
property_assignments: true
properties_in_camelcaps: true
prefer_while_loop_over_for_loop: true
prefer_unix_line_ending: true
prefer_sapi_constant: true
precedence_mistakes: true
precedence_in_conditions: true
phpunit_assertions: true
php5_style_constructor: true
parameters_in_camelcaps: true
parameter_non_unique: true
parameter_doc_comments: true
param_doc_comment_if_not_inferrable: true
overriding_private_members: true
optional_parameters_at_the_end: true
one_class_per_file: true
non_commented_empty_catch_block: true
no_unnecessary_if: true
no_unnecessary_function_call_in_for_loop: true
no_unnecessary_final_modifier: true
no_underscore_prefix_in_properties: true
no_underscore_prefix_in_methods: true
no_trait_type_hints: true
no_trailing_whitespace: true
no_space_inside_cast_operator: true
no_space_between_concatenation_operator: true
no_space_before_semicolon: true
no_space_around_object_operator: true
no_space_after_cast: true
no_short_open_tag: true
no_property_on_interface: true
no_non_implemented_abstract_methods: true
code_rating: true
duplication: true
deprecated_code_usage: true
closure_use_not_conflicting: true
closure_use_modifiable: true
avoid_useless_overridden_methods: true
avoid_conflicting_incrementers: true
assignment_of_null_return: true
no_goto: true
no_global_keyword: true
no_exit: true
no_eval: true
no_error_suppression: true
no_empty_statements: true
no_elseif_statements: true
no_duplicate_arguments: true
no_debug_code: true
no_commented_out_code: true
newline_at_end_of_file: true
naming_conventions:
local_variable: '^[a-z][a-zA-Z0-9]*$'
abstract_class_name: ^Abstract|Factory$
utility_class_name: 'Utils?$'
constant_name: '^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$'
property_name: '^[a-z][a-zA-Z0-9]*$'
method_name: '^(?:[a-z]|__)[a-zA-Z0-9]*$'
parameter_name: '^[a-z][a-zA-Z0-9]*$'
interface_name: '^[A-Z][a-zA-Z0-9]*Interface$'
type_name: '^[A-Z][a-zA-Z0-9]*$'
exception_name: '^[A-Z][a-zA-Z0-9]*Exception$'
isser_method_name: '^(?:is|has|should|may|supports|filter)'
more_specific_types_in_doc_comments: true
missing_arguments: true
method_calls_on_non_object: true
lowercase_php_keywords: true
lowercase_basic_constants: true
instanceof_class_exists: true
function_in_camel_caps: true
function_body_start_on_new_line: true
foreach_usable_as_reference: true
foreach_traversable: true
fix_use_statements:
remove_unused: true
preserve_multiple: false
preserve_blanklines: false
order_alphabetically: true
fix_php_opening_tag: true
fix_linefeed: true
fix_line_ending: true
fix_identation_4spaces: true
fix_doc_comments: true
ensure_lower_case_builtin_functions: true
encourage_single_quotes: true
encourage_shallow_comparison: true
encourage_postdec_operator: true
deadlock_detection_in_loops: true
classes_in_camel_caps: true
catch_class_exists: true
blank_line_after_namespace_declaration: true
avoid_usage_of_logical_operators: true
avoid_unnecessary_concatenation: true
avoid_todo_comments: true
avoid_tab_indentation: true
avoid_superglobals: true
avoid_perl_style_comments: true
avoid_fixme_comments: true
avoid_length_functions_in_loops: true
avoid_multiple_statements_on_same_line: true
avoid_entity_manager_injection: true
avoid_duplicate_types: true
avoid_corrupting_byteorder_marks: true
avoid_closing_tag: true
avoid_aliased_php_functions: true
align_assignments: true
argument_type_checks: true
no_mixed_inline_html: true
no_long_variable_names:
maximum: '20'

View File

@ -0,0 +1,31 @@
language: php
sudo: false
cache:
directories:
- $HOME/.composer/cache/files
matrix:
include:
- php: 5.6
- php: 5.6
env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable'
- php: 7.0
- php: hhvm
- php: nightly
allow_failures:
- php: 7.0
- php: nightly
fast_finish: true
before_install:
- travis_retry composer self-update && composer --version
install:
- travis_retry composer update $COMPOSER_FLAGS --prefer-source -n
script: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover
after_script:
- sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi;'

View File

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

View File

@ -0,0 +1,6 @@
test:
./vendor/bin/phpunit -c ./
code-coverage:
./vendor/bin/phpunit -c ./ --coverage-html=./docs/generated/code-coverage
open ./docs/generated/code-coverage/index.html

View File

@ -0,0 +1,125 @@
cocur/background-process
========================
> Start processes in the background that continue running when the PHP process exists.
[![Latest Stable Version](http://img.shields.io/packagist/v/cocur/background-process.svg)](https://packagist.org/packages/cocur/background-process)
[![Build Status](http://img.shields.io/travis/cocur/background-process.svg)](https://travis-ci.org/cocur/background-process)
[![Windows Build status](https://ci.appveyor.com/api/projects/status/odmyynd522vuef1y?svg=true)](https://ci.appveyor.com/project/florianeckerstorfer/background-process)
[![Code Coverage](https://scrutinizer-ci.com/g/cocur/background-process/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/cocur/background-process/?branch=master)
Installation
------------
You can install Cocur\BackgroundProcess using [Composer](http://getcomposer.org):
```shell
$ composer require cocur/background-process
```
Usage
-----
The following example will execute the command `sleep 5` in the background. Thus, if you run the following script
either in the browser or in the command line it will finish executing instantly.
```php
use Cocur\BackgroundProcess\BackgroundProcess;
$process = new BackgroundProcess('sleep 5');
$process->run();
```
You can retrieve the process ID (PID) of the process and check if it's running:
```php
use Cocur\BackgroundProcess\BackgroundProcess;
$process = new BackgroundProcess('sleep 5');
$process->run();
echo sprintf('Crunching numbers in process %d', $process->getPid());
while ($process->isRunning()) {
echo '.';
sleep(1);
}
echo "\nDone.\n";
```
If the process runs you can stop it:
```php
// ...
if ($process->isRunning()) {
$process->stop();
}
```
*Please note: If the parent process continues to run while the child process(es) run(s) in the background you should
use a more robust solution, for example, the [Symfony Process](https://github.com/symfony/Process) component.*
### Windows Support
Since Version 0.5 Cocur\BackgroundProcess has basic support for Windows included. However, support is very limited at
this time. You can run processes in the background, but it is not possible to direct the output into a file and you
can not retrieve the process ID (PID), check if a process is running and stop a running process.
In practice, the following methods will throw an exception if called on a Windows system:
- `Cocur\BackgroundProcess\BackgroundProcess::getPid()`
- `Cocur\BackgroundProcess\BackgroundProcess::isRunning()`
- `Cocur\BackgroundProcess\BackgroundProcess::stop()`
### Create with existing PID
If you have a long running process and store its PID in the database you might want to check at a later point (when you don't have the BackgroundProcess object anymore) whether the process is still running and stop the process.
```php
use Cocur\BackgroundProcess\BackgroundProcess;
$process = BackgroundProcess::createFromPID($pid);
$process->isRunning(); // -> true
$process->stop(); // -> true
```
Change Log
----------
### Version 0.7 (11 February 2017)
- [#19](https://github.com/cocur/background-process/pull/19) Create `BackgroundProcess` object from PID (by [socieboy](https://github.com/socieboy) and [florianeckerstorfer](https://github.com/florianeckerstorfer))
### Version 0.6 (10 July 2016)
- [#17](https://github.com/cocur/background-process/pull/17) Add ability to append to file on Unix/Linux-based systems (by [bpolaszek](https://github.com/bpolaszek))
### Version 0.5 (24 October 2015)
- Added basic support for Windows
### Version 0.4 (2 April 2014)
- Moved repository to Cocur organization
- Changed namespace to `Cocur`
- PSR-4 compatible namespace
- [#3](https://github.com/cocur/background-process/pull/3) Added `BackgroundProcess::stop()` (by florianeckerstorfer)
### Version 0.3 (15 November 2013)
- Changed namespace to `Braincrafted`
Author
------
[**Florian Eckerstorfer**](http://florian.ec)
- [Twitter](http://twitter.com/Florian_)
License
-------
The MIT license applies to **cocur/background-process**. For the full copyright and license information, please view the LICENSE file distributed with this source code.

View File

@ -0,0 +1,33 @@
build: false
shallow_clone: true
platform: x86
clone_folder: c:\projects\cocur\background-process
cache:
- '%LOCALAPPDATA%\Composer\files'
init:
- SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH%
environment:
matrix:
- COMPOSER_FLAGS: ""
install:
- cinst -y OpenSSL.Light
- cinst -y php
- cd c:\tools\php
- copy php.ini-production php.ini /Y
- echo date.timezone="UTC" >> php.ini
- echo extension_dir=ext >> php.ini
- echo extension=php_openssl.dll >> php.ini
- echo extension=php_mbstring.dll >> php.ini
- echo extension=php_fileinfo.dll >> php.ini
- echo memory_limit=1G >> php.ini
- cd c:\projects\cocur\background-process
- php -r "readfile('http://getcomposer.org/installer');" | php
- php composer.phar update %COMPOSER_FLAGS% --no-interaction --no-progress
test_script:
- cd c:\projects\cocur\background-process
- vendor\bin\phpunit.bat --verbose

View File

@ -0,0 +1,18 @@
{
"name": "cocur/background-process",
"description": "Start processes in the background that continue running when the PHP process exists.",
"type": "library",
"keywords": ["process", "background", "unix"],
"license": "MIT",
"require": {
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "~4.8|~5.1"
},
"autoload": {
"psr-4": {
"Cocur\\BackgroundProcess\\": "src/"
}
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" colors="true">
<testsuites>
<testsuite name="cocur/background-process Test Suite">
<directory>tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,198 @@
<?php
/**
* This file is part of cocur/background-process.
*
* (c) 2013-2015 Florian Eckerstorfer
*/
namespace Cocur\BackgroundProcess;
use Exception;
use RuntimeException;
/**
* BackgroundProcess.
*
* Runs a process in the background.
*
* @author Florian Eckerstorfer <florian@eckerstorfer.co>
* @copyright 2013-2015 Florian Eckerstorfer
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://florian.ec/articles/running-background-processes-in-php/ Running background processes in PHP
*/
class BackgroundProcess
{
const OS_WINDOWS = 1;
const OS_NIX = 2;
const OS_OTHER = 3;
/**
* @var string
*/
private $command;
/**
* @var int
*/
private $pid;
/**
* @var int
*/
protected $serverOS;
/**
* @param string $command The command to execute
*
* @codeCoverageIgnore
*/
public function __construct($command = null)
{
$this->command = $command;
$this->serverOS = $this->getOS();
}
/**
* Runs the command in a background process.
*
* @param string $outputFile File to write the output of the process to; defaults to /dev/null
* currently $outputFile has no effect when used in conjunction with a Windows server
* @param bool $append - set to true if output should be appended to $outputfile
*/
public function run($outputFile = '/dev/null', $append = false)
{
if($this->command === null) {
return;
}
switch ($this->getOS()) {
case self::OS_WINDOWS:
if (class_exists("COM")) {
$shell = new \COM("WScript.Shell");
$shell->CurrentDirectory = realpath(DOC_ROOT . "/exec");
$shell->run("cmd /C \"{$this->command}\"", 0, false);
} else {
shell_exec(sprintf('"cd ' . realpath(DOC_ROOT . "/exec") . ' && %s %s %s"', $this->command, ($append ? '>>' : '>'), $outputFile));
}
break;
case self::OS_NIX:
$script = "cd " . realpath(DOC_ROOT . "/exec") . " && " .
sprintf('%s %s %s 2>&1 &', $this->command, ($append ? ">>" : ">"), $outputFile);
pclose(popen($script, "r"));
break;
default:
throw new RuntimeException(sprintf(
'Could not execute command "%s" because operating system "%s" is not supported by '.
'Cocur\BackgroundProcess.', $this->command, PHP_OS
));
}
}
/**
* Returns if the process is currently running.
*
* @return bool TRUE if the process is running, FALSE if not.
*/
public function isRunning()
{
$this->checkSupportingOS('Cocur\BackgroundProcess can only check if a process is running on *nix-based '.
'systems, such as Unix, Linux or Mac OS X. You are running "%s".');
try {
$result = shell_exec(sprintf('ps %d 2>&1', $this->pid));
if (count(preg_split("/\n/", $result)) > 2 && !preg_match('/ERROR: Process ID out of range/', $result)) {
return true;
}
} catch (Exception $e) {
}
return false;
}
/**
* Stops the process.
*
* @return bool `true` if the processes was stopped, `false` otherwise.
*/
public function stop()
{
$this->checkSupportingOS('Cocur\BackgroundProcess can only stop a process on *nix-based systems, such as '.
'Unix, Linux or Mac OS X. You are running "%s".');
try {
$result = shell_exec(sprintf('kill %d 2>&1', $this->pid));
if (!preg_match('/No such process/', $result)) {
return true;
}
} catch (Exception $e) {
}
return false;
}
/**
* Returns the ID of the process.
*
* @return int The ID of the process
*/
public function getPid()
{
$this->checkSupportingOS('Cocur\BackgroundProcess can only return the PID of a process on *nix-based systems, '.
'such as Unix, Linux or Mac OS X. You are running "%s".');
return $this->pid;
}
/**
* Set the process id.
*
* @param $pid
*/
protected function setPid($pid)
{
$this->pid = $pid;
}
/**
* @return int
*/
protected function getOS()
{
$os = strtoupper(PHP_OS);
if (substr($os, 0, 3) === 'WIN') {
return self::OS_WINDOWS;
} elseif ($os === 'LINUX' || $os === 'FREEBSD' || $os === 'DARWIN') {
return self::OS_NIX;
}
return self::OS_OTHER;
}
/**
* @param string $message Exception message if the OS is not supported
*
* @throws RuntimeException if the operating system is not supported by Cocur\BackgroundProcess
*
* @codeCoverageIgnore
*/
protected function checkSupportingOS($message)
{
if ($this->getOS() !== self::OS_NIX) {
throw new RuntimeException(sprintf($message, PHP_OS));
}
}
/**
* @param int $pid PID of process to resume
*
* @return Cocur\BackgroundProcess\BackgroundProcess
*/
static public function createFromPID($pid) {
$process = new self();
$process->setPid($pid);
return $process;
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* This file is part of cocur/background-process.
*
* (c) 2013-2104 Florian Eckerstorfer
*/
namespace Cocur\BackgroundProcess;
/**
* Factory to create BackgroundProcess objects.
*
* @package cocur/background-process
* @author Florian Eckerstorfer <florian@eckerstorfer.co>
* @copyright 2013-2014 Florian Eckerstorfer
* @license http://opensource.org/licenses/MIT The MIT License
* @link http://braincrafted.com/php-background-processes/ Running background processes in PHP
*/
class Factory
{
/** @var string */
private $className;
public function __construct($className)
{
$this->className = $className;
}
public function newProcess($command)
{
$className = $this->className;
return new $className($command);
}
}

View File

@ -0,0 +1,206 @@
<?php
/**
* This file is part of cocur/background-process.
*
* (c) 2013-2014 Florian Eckerstorfer
*/
namespace Cocur\BackgroundProcess;
/**
* BackgroundProcessTest
*
* @category test
* @package cocur/background-process
* @author Florian Eckerstorfer <florian@eckerstorfer.co>
* @copyright 2013-2104 Florian Eckerstorfer
* @license http://opensource.org/licenses/MIT The MIT License
* @group functional
*/
class BackgroundProcessTest extends \PHPUnit_Framework_TestCase
{
/**
* @test
* @covers Cocur\BackgroundProcess\BackgroundProcess::run()
* @covers Cocur\BackgroundProcess\BackgroundProcess::isRunning()
* @covers Cocur\BackgroundProcess\BackgroundProcess::getOS()
*/
public function runShouldRunCommand()
{
if (preg_match('/^WIN/', PHP_OS)) {
$command = sprintf('tests\\fixtures\\cmd.bat', __DIR__);
} else {
$command = sprintf('./tests/fixtures/cmd.sh', __DIR__);
}
$checkFile = __DIR__.DIRECTORY_SEPARATOR.'fixtures'.DIRECTORY_SEPARATOR.'runShouldRunCommand.log';
$process = new BackgroundProcess($command);
$process->run();
sleep(1);
$this->assertStringStartsWith('ok', file_get_contents($checkFile));
unlink($checkFile);
}
/**
* @test
* @covers Cocur\BackgroundProcess\BackgroundProcess::isRunning()
* @covers Cocur\BackgroundProcess\BackgroundProcess::getOS()
*/
public function isRunningShouldReturnIfProcessIsRunning()
{
if (preg_match('/^WIN/', PHP_OS)) {
$this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::isRunning() is not supported on '.
'Windows.');
return;
}
$process = new BackgroundProcess('sleep 3');
$this->assertFalse($process->isRunning());
$process->run();
$this->assertTrue($process->isRunning());
}
/**
* @test
* @covers Cocur\BackgroundProcess\BackgroundProcess::isRunning()
* @expectedException \RuntimeException
*/
public function isRunningShouldThrowExceptionIfWindows()
{
if (!preg_match('/^WIN/', PHP_OS)) {
$this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::isRunning() is supported on *nix '.
'systems and does not need to throw an exception.');
return;
}
$process = new BackgroundProcess('sleep 1');
$process->isRunning();
}
/**
* @test
* @covers Cocur\BackgroundProcess\BackgroundProcess::run()
* @covers Cocur\BackgroundProcess\BackgroundProcess::getOS()
*/
public function runShouldWriteOutputToFile()
{
if (preg_match('/^WIN/', PHP_OS)) {
$this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::run() does not support writing output '.
'into a file on Windows.');
return;
}
$outputFile = __DIR__.'/fixtures/runShouldWriteOutputToFile.log';
$process = new BackgroundProcess('ls');
$process->run($outputFile);
sleep(1);
$this->assertNotNull(file_get_contents($outputFile));
unlink($outputFile);
}
/**
* @test
* @covers Cocur\BackgroundProcess\BackgroundProcess::getPid()
* @covers Cocur\BackgroundProcess\BackgroundProcess::getOS()
*/
public function getPidShouldReturnPidOfProcess()
{
if (preg_match('/^WIN/', PHP_OS)) {
$this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::getPid() is not supported on Windows.');
return;
}
$process = new BackgroundProcess('sleep 3');
$process->run();
$this->assertNotNull($process->getPid());
}
/**
* @test
* @covers Cocur\BackgroundProcess\BackgroundProcess::getPid()
* @expectedException \RuntimeException
*/
public function getPidShouldThrowExceptionIfWindows()
{
if (!preg_match('/^WIN/', PHP_OS)) {
$this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::getPid() is supported on *nix systems '.
'and does not need to throw an exception.');
return;
}
$process = new BackgroundProcess('sleep 1');
$process->getPid();
}
/**
* @test
* @covers Cocur\BackgroundProcess\BackgroundProcess::stop()
* @covers Cocur\BackgroundProcess\BackgroundProcess::getOS()
*/
public function stopShouldStopRunningProcess()
{
if (preg_match('/^WIN/', PHP_OS)) {
$this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::stop() is not supported on Windows.');
return;
}
$process = new BackgroundProcess('sleep 5');
$process->run();
$this->assertTrue($process->stop());
$this->assertFalse($process->isRunning());
}
/**
* @test
* @covers Cocur\BackgroundProcess\BackgroundProcess::stop()
* @expectedException \RuntimeException
*/
public function stopShouldThrowExceptionIfWindows()
{
if (!preg_match('/^WIN/', PHP_OS)) {
$this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::stop() is supported on *nix systems '.
'and does not need to throw an exception.');
return;
}
$process = new BackgroundProcess('sleep 1');
$process->stop();
}
/**
* @test
* @covers Cocur\BackgroundProcess\BackgroundProcess::createFromPID()
*/
public function createFromPIDShouldCreateObjectFromPID()
{
if (preg_match('/^WIN/', PHP_OS)) {
$this->markTestSkipped('Cocur\BackgroundProcess\BackgroundProcess::createFromPID() is not supported on Windows.');
return;
}
$process = new BackgroundProcess('sleep 1');
$process->run();
$pid = $process->getPid();
$newProcess = BackgroundProcess::createFromPID($pid);
$this->assertEquals($pid, $newProcess->getPid());
$this->assertTrue($newProcess->stop());
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* This file is part of cocur/background-process.
*
* (c) 2013-2014 Florian Eckerstorfer
*/
namespace Cocur\BackgroundProcess;
/**
* FactoryTest
*
* @category test
* @package cocur/background-process
* @author Florian Eckerstorfer <florian@eckerstorfer.co>
* @copyright 2013-2014 Florian Eckerstorfer
* @license http://opensource.org/licenses/MIT The MIT License
* @link http://braincrafted.com/php-background-processes/ Running background processes in PHP
* @group unit
*/
class FactoryTest extends \PHPUnit_Framework_TestCase
{
/** @var string */
private $mockClass = 'Cocur\BackgroundProcess\MockBackgroundProcess';
/**
* Tests the <code>newProcess</code> method.
*
* @covers Cocur\BackgroundProcess\Factory::__construct()
* @covers Cocur\BackgroundProcess\Factory::newProcess()
*/
public function testNewProcess()
{
$factory = new Factory($this->mockClass);
$this->assertInstanceOf($this->mockClass, $factory->newProcess('sleep 1'));
}
}
class MockBackgroundProcess
{
}

View File

@ -0,0 +1 @@
echo ok > tests\fixtures\runShouldRunCommand.log

View File

@ -0,0 +1,3 @@
#!/bin/bash
echo "ok" > tests/fixtures/runShouldRunCommand.log

445
inc/vendor/composer/ClassLoader.php vendored Normal file
View File

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

21
inc/vendor/composer/LICENSE vendored Normal file
View File

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

View File

@ -0,0 +1,23 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Datamatrix' => $vendorDir . '/tecnickcom/tcpdf/include/barcodes/datamatrix.php',
'PDF417' => $vendorDir . '/tecnickcom/tcpdf/include/barcodes/pdf417.php',
'QRcode' => $vendorDir . '/tecnickcom/tcpdf/include/barcodes/qrcode.php',
'TCPDF' => $vendorDir . '/tecnickcom/tcpdf/tcpdf.php',
'TCPDF2DBarcode' => $vendorDir . '/tecnickcom/tcpdf/tcpdf_barcodes_2d.php',
'TCPDFBarcode' => $vendorDir . '/tecnickcom/tcpdf/tcpdf_barcodes_1d.php',
'TCPDF_COLORS' => $vendorDir . '/tecnickcom/tcpdf/include/tcpdf_colors.php',
'TCPDF_FILTERS' => $vendorDir . '/tecnickcom/tcpdf/include/tcpdf_filters.php',
'TCPDF_FONTS' => $vendorDir . '/tecnickcom/tcpdf/include/tcpdf_fonts.php',
'TCPDF_FONT_DATA' => $vendorDir . '/tecnickcom/tcpdf/include/tcpdf_font_data.php',
'TCPDF_IMAGES' => $vendorDir . '/tecnickcom/tcpdf/include/tcpdf_images.php',
'TCPDF_IMPORT' => $vendorDir . '/tecnickcom/tcpdf/tcpdf_import.php',
'TCPDF_PARSER' => $vendorDir . '/tecnickcom/tcpdf/tcpdf_parser.php',
'TCPDF_STATIC' => $vendorDir . '/tecnickcom/tcpdf/include/tcpdf_static.php',
);

View File

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

14
inc/vendor/composer/autoload_psr4.php vendored Normal file
View File

@ -0,0 +1,14 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'PhpOffice\\PhpSpreadsheet\\' => array($vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'Cocur\\BackgroundProcess\\' => array($vendorDir . '/cocur/background-process/src'),
);

52
inc/vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,52 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit1fa72dab7423f549dd6a0578a12d3ab4
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit1fa72dab7423f549dd6a0578a12d3ab4', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit1fa72dab7423f549dd6a0578a12d3ab4', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit1fa72dab7423f549dd6a0578a12d3ab4::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
return $loader;
}
}

75
inc/vendor/composer/autoload_static.php vendored Normal file
View File

@ -0,0 +1,75 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit1fa72dab7423f549dd6a0578a12d3ab4
{
public static $prefixLengthsPsr4 = array (
'P' =>
array (
'Psr\\SimpleCache\\' => 16,
'Psr\\Log\\' => 8,
'PhpOffice\\PhpSpreadsheet\\' => 25,
),
'M' =>
array (
'Monolog\\' => 8,
),
'C' =>
array (
'Cocur\\BackgroundProcess\\' => 24,
),
);
public static $prefixDirsPsr4 = array (
'Psr\\SimpleCache\\' =>
array (
0 => __DIR__ . '/..' . '/psr/simple-cache/src',
),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
),
'PhpOffice\\PhpSpreadsheet\\' =>
array (
0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet',
),
'Monolog\\' =>
array (
0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
),
'Cocur\\BackgroundProcess\\' =>
array (
0 => __DIR__ . '/..' . '/cocur/background-process/src',
),
);
public static $classMap = array (
'Datamatrix' => __DIR__ . '/..' . '/tecnickcom/tcpdf/include/barcodes/datamatrix.php',
'PDF417' => __DIR__ . '/..' . '/tecnickcom/tcpdf/include/barcodes/pdf417.php',
'QRcode' => __DIR__ . '/..' . '/tecnickcom/tcpdf/include/barcodes/qrcode.php',
'TCPDF' => __DIR__ . '/..' . '/tecnickcom/tcpdf/tcpdf.php',
'TCPDF2DBarcode' => __DIR__ . '/..' . '/tecnickcom/tcpdf/tcpdf_barcodes_2d.php',
'TCPDFBarcode' => __DIR__ . '/..' . '/tecnickcom/tcpdf/tcpdf_barcodes_1d.php',
'TCPDF_COLORS' => __DIR__ . '/..' . '/tecnickcom/tcpdf/include/tcpdf_colors.php',
'TCPDF_FILTERS' => __DIR__ . '/..' . '/tecnickcom/tcpdf/include/tcpdf_filters.php',
'TCPDF_FONTS' => __DIR__ . '/..' . '/tecnickcom/tcpdf/include/tcpdf_fonts.php',
'TCPDF_FONT_DATA' => __DIR__ . '/..' . '/tecnickcom/tcpdf/include/tcpdf_font_data.php',
'TCPDF_IMAGES' => __DIR__ . '/..' . '/tecnickcom/tcpdf/include/tcpdf_images.php',
'TCPDF_IMPORT' => __DIR__ . '/..' . '/tecnickcom/tcpdf/tcpdf_import.php',
'TCPDF_PARSER' => __DIR__ . '/..' . '/tecnickcom/tcpdf/tcpdf_parser.php',
'TCPDF_STATIC' => __DIR__ . '/..' . '/tecnickcom/tcpdf/include/tcpdf_static.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit1fa72dab7423f549dd6a0578a12d3ab4::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit1fa72dab7423f549dd6a0578a12d3ab4::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit1fa72dab7423f549dd6a0578a12d3ab4::$classMap;
}, null, ClassLoader::class);
}
}

394
inc/vendor/composer/installed.json vendored Normal file
View File

@ -0,0 +1,394 @@
[
{
"name": "cocur/background-process",
"version": "v0.7",
"version_normalized": "0.7.0.0",
"source": {
"type": "git",
"url": "https://github.com/cocur/background-process.git",
"reference": "9ae2902922f02ec5544d723756758eb7304c6869"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cocur/background-process/zipball/9ae2902922f02ec5544d723756758eb7304c6869",
"reference": "9ae2902922f02ec5544d723756758eb7304c6869",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "~4.8|~5.1"
},
"time": "2017-02-11T12:41:41+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Cocur\\BackgroundProcess\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Start processes in the background that continue running when the PHP process exists.",
"keywords": [
"background",
"process",
"unix"
]
},
{
"name": "monolog/monolog",
"version": "1.23.0",
"version_normalized": "1.23.0.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
"reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"psr/log": "~1.0"
},
"provide": {
"psr/log-implementation": "1.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"graylog2/gelf-php": "~1.0",
"jakub-onderka/php-parallel-lint": "0.9",
"php-amqplib/php-amqplib": "~2.4",
"php-console/php-console": "^3.1.3",
"phpunit/phpunit": "~4.5",
"phpunit/phpunit-mock-objects": "2.3.0",
"ruflin/elastica": ">=0.90 <3.0",
"sentry/sentry": "^0.13",
"swiftmailer/swiftmailer": "^5.3|^6.0"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongo": "Allow sending log messages to a MongoDB server",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"php-console/php-console": "Allow sending log messages to Google Chrome",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
"sentry/sentry": "Allow sending log messages to a Sentry server"
},
"time": "2017-06-19T01:22:40+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "http://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
]
},
{
"name": "pacificsec/cpe",
"version": "1.0.0",
"version_normalized": "1.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/pacificsec/cpe.git",
"reference": "3d78d66fc4ea249b6f353a7c48f426835a792d11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pacificsec/cpe/zipball/3d78d66fc4ea249b6f353a7c48f426835a792d11",
"reference": "3d78d66fc4ea249b6f353a7c48f426835a792d11",
"shasum": ""
},
"type": "library",
"installation-source": "dist",
"notification-url": "https://packagist.org/downloads/"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "1.1.0",
"version_normalized": "1.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "a2771e562e3a17c0d512d2009e38fd628beece90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/a2771e562e3a17c0d512d2009e38fd628beece90",
"reference": "a2771e562e3a17c0d512d2009e38fd628beece90",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"php": "^5.6|^7.0",
"psr/simple-cache": "^1.0"
},
"require-dev": {
"dompdf/dompdf": "^0.8.0",
"friendsofphp/php-cs-fixer": "@stable",
"jpgraph/jpgraph": "^4.0",
"mpdf/mpdf": "^7.0.0",
"phpunit/phpunit": "^5.7",
"squizlabs/php_codesniffer": "^2.7",
"tecnickcom/tcpdf": "^6.2"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-dom": "Option to read and write HTML files",
"ext-gd": "Required for exact column width autocalculation",
"jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnick.com/tcpdf": "Option for rendering PDF with PDF Writer"
},
"time": "2018-01-28T12:37:15+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "http://blog.maartenballiauw.be"
},
{
"name": "Erik Tilt"
},
{
"name": "Franck Lefevre",
"homepage": "http://rootslabs.net"
},
{
"name": "Mark Baker",
"homepage": "http://markbakeruk.net"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
]
},
{
"name": "psr/log",
"version": "1.0.2",
"version_normalized": "1.0.2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2016-10-10T12:19:37+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
]
},
{
"name": "psr/simple-cache",
"version": "1.0.0",
"version_normalized": "1.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/simple-cache.git",
"reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/753fa598e8f3b9966c886fe13f370baa45ef0e24",
"reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2017-01-02T13:31:39+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\SimpleCache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interfaces for simple caching",
"keywords": [
"cache",
"caching",
"psr",
"psr-16",
"simple-cache"
]
},
{
"name": "tecnickcom/tcpdf",
"version": "6.2.17",
"version_normalized": "6.2.17.0",
"source": {
"type": "git",
"url": "https://github.com/tecnickcom/TCPDF.git",
"reference": "64fc19439863e1b1314487a72a74d9bfd0b55a53"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/64fc19439863e1b1314487a72a74d9bfd0b55a53",
"reference": "64fc19439863e1b1314487a72a74d9bfd0b55a53",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2018-02-24T11:48:20+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"classmap": [
"config",
"include",
"tcpdf.php",
"tcpdf_parser.php",
"tcpdf_import.php",
"tcpdf_barcodes_1d.php",
"tcpdf_barcodes_2d.php",
"include/tcpdf_colors.php",
"include/tcpdf_filters.php",
"include/tcpdf_font_data.php",
"include/tcpdf_fonts.php",
"include/tcpdf_images.php",
"include/tcpdf_static.php",
"include/barcodes/datamatrix.php",
"include/barcodes/pdf417.php",
"include/barcodes/qrcode.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"authors": [
{
"name": "Nicola Asuni",
"email": "info@tecnick.com",
"role": "lead"
}
],
"description": "TCPDF is a PHP class for generating PDF documents and barcodes.",
"homepage": "http://www.tcpdf.org/",
"keywords": [
"PDFD32000-2008",
"TCPDF",
"barcodes",
"datamatrix",
"pdf",
"pdf417",
"qrcode"
]
}
]

59
inc/vendor/monolog/monolog/.php_cs vendored Normal file
View File

@ -0,0 +1,59 @@
<?php
$header = <<<EOF
This file is part of the Monolog package.
(c) Jordi Boggiano <j.boggiano@seld.be>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
EOF;
$finder = Symfony\CS\Finder::create()
->files()
->name('*.php')
->exclude('Fixtures')
->in(__DIR__.'/src')
->in(__DIR__.'/tests')
;
return Symfony\CS\Config::create()
->setUsingCache(true)
//->setUsingLinter(false)
->setRiskyAllowed(true)
->setRules(array(
'@PSR2' => true,
'binary_operator_spaces' => true,
'blank_line_before_return' => true,
'header_comment' => array('header' => $header),
'include' => true,
'long_array_syntax' => true,
'method_separation' => true,
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_blank_lines_between_uses' => true,
'no_duplicate_semicolons' => true,
'no_extra_consecutive_blank_lines' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_unused_imports' => true,
'object_operator_without_whitespace' => true,
'phpdoc_align' => true,
'phpdoc_indent' => true,
'phpdoc_no_access' => true,
'phpdoc_no_package' => true,
'phpdoc_order' => true,
'phpdoc_scalar' => true,
'phpdoc_trim' => true,
'phpdoc_type_to_var' => true,
'psr0' => true,
'single_blank_line_before_namespace' => true,
'spaces_cast' => true,
'standardize_not_equals' => true,
'ternary_operator_spaces' => true,
'trailing_comma_in_multiline_array' => true,
'whitespacy_lines' => true,
))
->finder($finder)
;

342
inc/vendor/monolog/monolog/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,342 @@
### 1.23.0 (2017-06-19)
* Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument
* Fixed GelfHandler truncation to be per field and not per message
* Fixed compatibility issue with PHP <5.3.6
* Fixed support for headless Chrome in ChromePHPHandler
* Fixed support for latest Aws SDK in DynamoDbHandler
* Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler
### 1.22.1 (2017-03-13)
* Fixed lots of minor issues in the new Slack integrations
* Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces
### 1.22.0 (2016-11-26)
* Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily
* Added MercurialProcessor to add mercurial revision and branch names to log records
* Added support for AWS SDK v3 in DynamoDbHandler
* Fixed fatal errors occuring when normalizing generators that have been fully consumed
* Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix)
* Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore
* Fixed SyslogUdpHandler to avoid sending empty frames
* Fixed a few PHP 7.0 and 7.1 compatibility issues
### 1.21.0 (2016-07-29)
* Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues
* Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order
* Added ability to format the main line of text the SlackHandler sends by explictly setting a formatter on the handler
* Added information about SoapFault instances in NormalizerFormatter
* Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level
### 1.20.0 (2016-07-02)
* Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy
* Added StreamHandler::getUrl to retrieve the stream's URL
* Added ability to override addRow/addTitle in HtmlFormatter
* Added the $context to context information when the ErrorHandler handles a regular php error
* Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d
* Fixed WhatFailureGroupHandler to work with PHP7 throwables
* Fixed a few minor bugs
### 1.19.0 (2016-04-12)
* Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed
* Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors
* Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler
* Fixed HipChatHandler handling of long messages
### 1.18.2 (2016-04-02)
* Fixed ElasticaFormatter to use more precise dates
* Fixed GelfMessageFormatter sending too long messages
### 1.18.1 (2016-03-13)
* Fixed SlackHandler bug where slack dropped messages randomly
* Fixed RedisHandler issue when using with the PHPRedis extension
* Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension
* Fixed BrowserConsoleHandler regression
### 1.18.0 (2016-03-01)
* Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond
* Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames
* Added `Logger->withName` to clone a logger (keeping all handlers) with a new name
* Added FluentdFormatter for the Fluentd unix socket protocol
* Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed
* Added support for replacing context sub-keys using `%context.*%` in LineFormatter
* Added support for `payload` context value in RollbarHandler
* Added setRelease to RavenHandler to describe the application version, sent with every log
* Added support for `fingerprint` context value in RavenHandler
* Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed
* Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()`
* Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places
### 1.17.2 (2015-10-14)
* Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers
* Fixed SlackHandler handling to use slack functionalities better
* Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id
* Fixed 5.3 compatibility regression
### 1.17.1 (2015-08-31)
* Fixed RollbarHandler triggering PHP notices
### 1.17.0 (2015-08-30)
* Added support for `checksum` and `release` context/extra values in RavenHandler
* Added better support for exceptions in RollbarHandler
* Added UidProcessor::getUid
* Added support for showing the resource type in NormalizedFormatter
* Fixed IntrospectionProcessor triggering PHP notices
### 1.16.0 (2015-08-09)
* Added IFTTTHandler to notify ifttt.com triggers
* Added Logger::setHandlers() to allow setting/replacing all handlers
* Added $capSize in RedisHandler to cap the log size
* Fixed StreamHandler creation of directory to only trigger when the first log write happens
* Fixed bug in the handling of curl failures
* Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler
* Fixed missing fatal errors records with handlers that need to be closed to flush log records
* Fixed TagProcessor::addTags support for associative arrays
### 1.15.0 (2015-07-12)
* Added addTags and setTags methods to change a TagProcessor
* Added automatic creation of directories if they are missing for a StreamHandler to open a log file
* Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure
* Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used
* Fixed HTML/JS escaping in BrowserConsoleHandler
* Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only)
### 1.14.0 (2015-06-19)
* Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library
* Added support for objects implementing __toString in the NormalizerFormatter
* Added support for HipChat's v2 API in HipChatHandler
* Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app
* Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true)
* Fixed curl errors being silently suppressed
### 1.13.1 (2015-03-09)
* Fixed regression in HipChat requiring a new token to be created
### 1.13.0 (2015-03-05)
* Added Registry::hasLogger to check for the presence of a logger instance
* Added context.user support to RavenHandler
* Added HipChat API v2 support in the HipChatHandler
* Added NativeMailerHandler::addParameter to pass params to the mail() process
* Added context data to SlackHandler when $includeContextAndExtra is true
* Added ability to customize the Swift_Message per-email in SwiftMailerHandler
* Fixed SwiftMailerHandler to lazily create message instances if a callback is provided
* Fixed serialization of INF and NaN values in Normalizer and LineFormatter
### 1.12.0 (2014-12-29)
* Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers.
* Added PsrHandler to forward records to another PSR-3 logger
* Added SamplingHandler to wrap around a handler and include only every Nth record
* Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now)
* Added exception codes in the output of most formatters
* Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line)
* Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data
* Added $host to HipChatHandler for users of private instances
* Added $transactionName to NewRelicHandler and support for a transaction_name context value
* Fixed MandrillHandler to avoid outputing API call responses
* Fixed some non-standard behaviors in SyslogUdpHandler
### 1.11.0 (2014-09-30)
* Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names
* Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails
* Added MandrillHandler to send emails via the Mandrillapp.com API
* Added SlackHandler to log records to a Slack.com account
* Added FleepHookHandler to log records to a Fleep.io account
* Added LogglyHandler::addTag to allow adding tags to an existing handler
* Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end
* Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing
* Added support for PhpAmqpLib in the AmqpHandler
* Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs
* Added support for adding extra fields from $_SERVER in the WebProcessor
* Fixed support for non-string values in PrsLogMessageProcessor
* Fixed SwiftMailer messages being sent with the wrong date in long running scripts
* Fixed minor PHP 5.6 compatibility issues
* Fixed BufferHandler::close being called twice
### 1.10.0 (2014-06-04)
* Added Logger::getHandlers() and Logger::getProcessors() methods
* Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached
* Added support for extra data in NewRelicHandler
* Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines
### 1.9.1 (2014-04-24)
* Fixed regression in RotatingFileHandler file permissions
* Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records
* Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative
### 1.9.0 (2014-04-20)
* Added LogEntriesHandler to send logs to a LogEntries account
* Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler
* Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes
* Added support for table formatting in FirePHPHandler via the table context key
* Added a TagProcessor to add tags to records, and support for tags in RavenHandler
* Added $appendNewline flag to the JsonFormatter to enable using it when logging to files
* Added sound support to the PushoverHandler
* Fixed multi-threading support in StreamHandler
* Fixed empty headers issue when ChromePHPHandler received no records
* Fixed default format of the ErrorLogHandler
### 1.8.0 (2014-03-23)
* Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them
* Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output
* Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler
* Added FlowdockHandler to send logs to a Flowdock account
* Added RollbarHandler to send logs to a Rollbar account
* Added HtmlFormatter to send prettier log emails with colors for each log level
* Added GitProcessor to add the current branch/commit to extra record data
* Added a Monolog\Registry class to allow easier global access to pre-configured loggers
* Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement
* Added support for HHVM
* Added support for Loggly batch uploads
* Added support for tweaking the content type and encoding in NativeMailerHandler
* Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor
* Fixed batch request support in GelfHandler
### 1.7.0 (2013-11-14)
* Added ElasticSearchHandler to send logs to an Elastic Search server
* Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB
* Added SyslogUdpHandler to send logs to a remote syslogd server
* Added LogglyHandler to send logs to a Loggly account
* Added $level to IntrospectionProcessor so it only adds backtraces when needed
* Added $version to LogstashFormatter to allow using the new v1 Logstash format
* Added $appName to NewRelicHandler
* Added configuration of Pushover notification retries/expiry
* Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default
* Added chainability to most setters for all handlers
* Fixed RavenHandler batch processing so it takes the message from the record with highest priority
* Fixed HipChatHandler batch processing so it sends all messages at once
* Fixed issues with eAccelerator
* Fixed and improved many small things
### 1.6.0 (2013-07-29)
* Added HipChatHandler to send logs to a HipChat chat room
* Added ErrorLogHandler to send logs to PHP's error_log function
* Added NewRelicHandler to send logs to NewRelic's service
* Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler
* Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel
* Added stack traces output when normalizing exceptions (json output & co)
* Added Monolog\Logger::API constant (currently 1)
* Added support for ChromePHP's v4.0 extension
* Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel
* Added support for sending messages to multiple users at once with the PushoverHandler
* Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler)
* Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now
* Fixed issue in RotatingFileHandler when an open_basedir restriction is active
* Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0
* Fixed SyslogHandler issue when many were used concurrently with different facilities
### 1.5.0 (2013-04-23)
* Added ProcessIdProcessor to inject the PID in log records
* Added UidProcessor to inject a unique identifier to all log records of one request/run
* Added support for previous exceptions in the LineFormatter exception serialization
* Added Monolog\Logger::getLevels() to get all available levels
* Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle
### 1.4.1 (2013-04-01)
* Fixed exception formatting in the LineFormatter to be more minimalistic
* Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0
* Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days
* Fixed WebProcessor array access so it checks for data presence
* Fixed Buffer, Group and FingersCrossed handlers to make use of their processors
### 1.4.0 (2013-02-13)
* Added RedisHandler to log to Redis via the Predis library or the phpredis extension
* Added ZendMonitorHandler to log to the Zend Server monitor
* Added the possibility to pass arrays of handlers and processors directly in the Logger constructor
* Added `$useSSL` option to the PushoverHandler which is enabled by default
* Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously
* Fixed header injection capability in the NativeMailHandler
### 1.3.1 (2013-01-11)
* Fixed LogstashFormatter to be usable with stream handlers
* Fixed GelfMessageFormatter levels on Windows
### 1.3.0 (2013-01-08)
* Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface`
* Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance
* Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash)
* Added PushoverHandler to send mobile notifications
* Added CouchDBHandler and DoctrineCouchDBHandler
* Added RavenHandler to send data to Sentry servers
* Added support for the new MongoClient class in MongoDBHandler
* Added microsecond precision to log records' timestamps
* Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing
the oldest entries
* Fixed normalization of objects with cyclic references
### 1.2.1 (2012-08-29)
* Added new $logopts arg to SyslogHandler to provide custom openlog options
* Fixed fatal error in SyslogHandler
### 1.2.0 (2012-08-18)
* Added AmqpHandler (for use with AMQP servers)
* Added CubeHandler
* Added NativeMailerHandler::addHeader() to send custom headers in mails
* Added the possibility to specify more than one recipient in NativeMailerHandler
* Added the possibility to specify float timeouts in SocketHandler
* Added NOTICE and EMERGENCY levels to conform with RFC 5424
* Fixed the log records to use the php default timezone instead of UTC
* Fixed BufferHandler not being flushed properly on PHP fatal errors
* Fixed normalization of exotic resource types
* Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog
### 1.1.0 (2012-04-23)
* Added Monolog\Logger::isHandling() to check if a handler will
handle the given log level
* Added ChromePHPHandler
* Added MongoDBHandler
* Added GelfHandler (for use with Graylog2 servers)
* Added SocketHandler (for use with syslog-ng for example)
* Added NormalizerFormatter
* Added the possibility to change the activation strategy of the FingersCrossedHandler
* Added possibility to show microseconds in logs
* Added `server` and `referer` to WebProcessor output
### 1.0.2 (2011-10-24)
* Fixed bug in IE with large response headers and FirePHPHandler
### 1.0.1 (2011-08-25)
* Added MemoryPeakUsageProcessor and MemoryUsageProcessor
* Added Monolog\Logger::getName() to get a logger's channel name
### 1.0.0 (2011-07-06)
* Added IntrospectionProcessor to get info from where the logger was called
* Fixed WebProcessor in CLI
### 1.0.0-RC1 (2011-07-01)
* Initial release

19
inc/vendor/monolog/monolog/LICENSE vendored Normal file
View File

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

95
inc/vendor/monolog/monolog/README.md vendored Normal file
View File

@ -0,0 +1,95 @@
# Monolog - Logging for PHP [![Build Status](https://img.shields.io/travis/Seldaek/monolog.svg)](https://travis-ci.org/Seldaek/monolog)
[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)
[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)
[![Reference Status](https://www.versioneye.com/php/monolog:monolog/reference_badge.svg)](https://www.versioneye.com/php/monolog:monolog/references)
Monolog sends your logs to files, sockets, inboxes, databases and various
web services. See the complete list of handlers below. Special handlers
allow you to build advanced logging strategies.
This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
interface that you can type-hint against in your own libraries to keep
a maximum of interoperability. You can also use it in your applications to
make sure you can always use another compatible logger at a later time.
As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels.
Internally Monolog still uses its own level scheme since it predates PSR-3.
## Installation
Install the latest version with
```bash
$ composer require monolog/monolog
```
## Basic Usage
```php
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
// add records to the log
$log->addWarning('Foo');
$log->addError('Bar');
```
## Documentation
- [Usage Instructions](doc/01-usage.md)
- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md)
- [Utility classes](doc/03-utilities.md)
- [Extending Monolog](doc/04-extending.md)
## Third Party Packages
Third party handlers, formatters and processors are
[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You
can also add your own there if you publish one.
## About
### Requirements
- Monolog works with PHP 5.3 or above, and is also tested to work with HHVM.
### Submitting bugs and feature requests
Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues)
### Framework Integrations
- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
can be used very easily with Monolog since it implements the interface.
- [Symfony2](http://symfony.com) comes out of the box with Monolog.
- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog.
- [Laravel 4 & 5](http://laravel.com/) come out of the box with Monolog.
- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog.
- [PPI](http://www.ppi.io/) comes out of the box with Monolog.
- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin.
- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer.
- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog.
- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog.
- [Nette Framework](http://nette.org/en/) can be used with Monolog via [Kdyby/Monolog](https://github.com/Kdyby/Monolog) extension.
- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog.
### Author
Jordi Boggiano - <j.boggiano@seld.be> - <http://twitter.com/seldaek><br />
See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project.
### License
Monolog is licensed under the MIT License - see the `LICENSE` file for details
### Acknowledgements
This library is heavily inspired by Python's [Logbook](http://packages.python.org/Logbook/)
library, although most concepts have been adjusted to fit to the PHP world.

View File

@ -0,0 +1,66 @@
{
"name": "monolog/monolog",
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"keywords": ["log", "logging", "psr-3"],
"homepage": "http://github.com/Seldaek/monolog",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"require": {
"php": ">=5.3.0",
"psr/log": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.5",
"graylog2/gelf-php": "~1.0",
"sentry/sentry": "^0.13",
"ruflin/elastica": ">=0.90 <3.0",
"doctrine/couchdb": "~1.0@dev",
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"php-amqplib/php-amqplib": "~2.4",
"swiftmailer/swiftmailer": "^5.3|^6.0",
"php-console/php-console": "^3.1.3",
"phpunit/phpunit-mock-objects": "2.3.0",
"jakub-onderka/php-parallel-lint": "0.9"
},
"_": "phpunit/phpunit-mock-objects required in 2.3.0 due to https://github.com/sebastianbergmann/phpunit-mock-objects/issues/223 - needs hhvm 3.8+ on travis",
"suggest": {
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"sentry/sentry": "Allow sending log messages to a Sentry server",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongo": "Allow sending log messages to a MongoDB server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"php-console/php-console": "Allow sending log messages to Google Chrome"
},
"autoload": {
"psr-4": {"Monolog\\": "src/Monolog"}
},
"autoload-dev": {
"psr-4": {"Monolog\\": "tests/Monolog"}
},
"provide": {
"psr/log-implementation": "1.0.0"
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"scripts": {
"test": [
"parallel-lint . --exclude vendor",
"phpunit"
]
}
}

View File

@ -0,0 +1,231 @@
# Using Monolog
- [Installation](#installation)
- [Core Concepts](#core-concepts)
- [Log Levels](#log-levels)
- [Configuring a logger](#configuring-a-logger)
- [Adding extra data in the records](#adding-extra-data-in-the-records)
- [Leveraging channels](#leveraging-channels)
- [Customizing the log format](#customizing-the-log-format)
## Installation
Monolog is available on Packagist ([monolog/monolog](http://packagist.org/packages/monolog/monolog))
and as such installable via [Composer](http://getcomposer.org/).
```bash
composer require monolog/monolog
```
If you do not use Composer, you can grab the code from GitHub, and use any
PSR-0 compatible autoloader (e.g. the [Symfony2 ClassLoader component](https://github.com/symfony/ClassLoader))
to load Monolog classes.
## Core Concepts
Every `Logger` instance has a channel (name) and a stack of handlers. Whenever
you add a record to the logger, it traverses the handler stack. Each handler
decides whether it fully handled the record, and if so, the propagation of the
record ends there.
This allows for flexible logging setups, for example having a `StreamHandler` at
the bottom of the stack that will log anything to disk, and on top of that add
a `MailHandler` that will send emails only when an error message is logged.
Handlers also have a `$bubble` property which defines whether they block the
record or not if they handled it. In this example, setting the `MailHandler`'s
`$bubble` argument to false means that records handled by the `MailHandler` will
not propagate to the `StreamHandler` anymore.
You can create many `Logger`s, each defining a channel (e.g.: db, request,
router, ..) and each of them combining various handlers, which can be shared
or not. The channel is reflected in the logs and allows you to easily see or
filter records.
Each Handler also has a Formatter, a default one with settings that make sense
will be created if you don't set one. The formatters normalize and format
incoming records so that they can be used by the handlers to output useful
information.
Custom severity levels are not available. Only the eight
[RFC 5424](http://tools.ietf.org/html/rfc5424) levels (debug, info, notice,
warning, error, critical, alert, emergency) are present for basic filtering
purposes, but for sorting and other use cases that would require
flexibility, you should add Processors to the Logger that can add extra
information (tags, user ip, ..) to the records before they are handled.
## Log Levels
Monolog supports the logging levels described by [RFC 5424](http://tools.ietf.org/html/rfc5424).
- **DEBUG** (100): Detailed debug information.
- **INFO** (200): Interesting events. Examples: User logs in, SQL logs.
- **NOTICE** (250): Normal but significant events.
- **WARNING** (300): Exceptional occurrences that are not errors. Examples:
Use of deprecated APIs, poor use of an API, undesirable things that are not
necessarily wrong.
- **ERROR** (400): Runtime errors that do not require immediate action but
should typically be logged and monitored.
- **CRITICAL** (500): Critical conditions. Example: Application component
unavailable, unexpected exception.
- **ALERT** (550): Action must be taken immediately. Example: Entire website
down, database unavailable, etc. This should trigger the SMS alerts and wake
you up.
- **EMERGENCY** (600): Emergency: system is unusable.
## Configuring a logger
Here is a basic setup to log to a file and to firephp on the DEBUG level:
```php
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;
// Create the logger
$logger = new Logger('my_logger');
// Now add some handlers
$logger->pushHandler(new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG));
$logger->pushHandler(new FirePHPHandler());
// You can now use your logger
$logger->addInfo('My logger is now ready');
```
Let's explain it. The first step is to create the logger instance which will
be used in your code. The argument is a channel name, which is useful when
you use several loggers (see below for more details about it).
The logger itself does not know how to handle a record. It delegates it to
some handlers. The code above registers two handlers in the stack to allow
handling records in two different ways.
Note that the FirePHPHandler is called first as it is added on top of the
stack. This allows you to temporarily add a logger with bubbling disabled if
you want to override other configured loggers.
> If you use Monolog standalone and are looking for an easy way to
> configure many handlers, the [theorchard/monolog-cascade](https://github.com/theorchard/monolog-cascade)
> can help you build complex logging configs via PHP arrays, yaml or json configs.
## Adding extra data in the records
Monolog provides two different ways to add extra informations along the simple
textual message.
### Using the logging context
The first way is the context, allowing to pass an array of data along the
record:
```php
<?php
$logger->addInfo('Adding a new user', array('username' => 'Seldaek'));
```
Simple handlers (like the StreamHandler for instance) will simply format
the array to a string but richer handlers can take advantage of the context
(FirePHP is able to display arrays in pretty way for instance).
### Using processors
The second way is to add extra data for all records by using a processor.
Processors can be any callable. They will get the record as parameter and
must return it after having eventually changed the `extra` part of it. Let's
write a processor adding some dummy data in the record:
```php
<?php
$logger->pushProcessor(function ($record) {
$record['extra']['dummy'] = 'Hello world!';
return $record;
});
```
Monolog provides some built-in processors that can be used in your project.
Look at the [dedicated chapter](https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md#processors) for the list.
> Tip: processors can also be registered on a specific handler instead of
the logger to apply only for this handler.
## Leveraging channels
Channels are a great way to identify to which part of the application a record
is related. This is useful in big applications (and is leveraged by
MonologBundle in Symfony2).
Picture two loggers sharing a handler that writes to a single log file.
Channels would allow you to identify the logger that issued every record.
You can easily grep through the log files filtering this or that channel.
```php
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;
// Create some handlers
$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG);
$firephp = new FirePHPHandler();
// Create the main logger of the app
$logger = new Logger('my_logger');
$logger->pushHandler($stream);
$logger->pushHandler($firephp);
// Create a logger for the security-related stuff with a different channel
$securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
$securityLogger->pushHandler($firephp);
// Or clone the first one to only change the channel
$securityLogger = $logger->withName('security');
```
## Customizing the log format
In Monolog it's easy to customize the format of the logs written into files,
sockets, mails, databases and other handlers. Most of the handlers use the
```php
$record['formatted']
```
value to be automatically put into the log device. This value depends on the
formatter settings. You can choose between predefined formatter classes or
write your own (e.g. a multiline text file for human-readable output).
To configure a predefined formatter class, just set it as the handler's field:
```php
// the default date format is "Y-m-d H:i:s"
$dateFormat = "Y n j, g:i a";
// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"
$output = "%datetime% > %level_name% > %message% %context% %extra%\n";
// finally, create a formatter
$formatter = new LineFormatter($output, $dateFormat);
// Create a handler
$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG);
$stream->setFormatter($formatter);
// bind it to a logger object
$securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
```
You may also reuse the same formatter between multiple handlers and share those
handlers between multiple loggers.
[Handlers, Formatters and Processors](02-handlers-formatters-processors.md) &rarr;

View File

@ -0,0 +1,157 @@
# Handlers, Formatters and Processors
- [Handlers](#handlers)
- [Log to files and syslog](#log-to-files-and-syslog)
- [Send alerts and emails](#send-alerts-and-emails)
- [Log specific servers and networked logging](#log-specific-servers-and-networked-logging)
- [Logging in development](#logging-in-development)
- [Log to databases](#log-to-databases)
- [Wrappers / Special Handlers](#wrappers--special-handlers)
- [Formatters](#formatters)
- [Processors](#processors)
- [Third Party Packages](#third-party-packages)
## Handlers
### Log to files and syslog
- _StreamHandler_: Logs records into any PHP stream, use this for log files.
- _RotatingFileHandler_: Logs records to a file and creates one logfile per day.
It will also delete files older than `$maxFiles`. You should use
[logrotate](http://linuxcommand.org/man_pages/logrotate8.html) for high profile
setups though, this is just meant as a quick and dirty solution.
- _SyslogHandler_: Logs records to the syslog.
- _ErrorLogHandler_: Logs records to PHP's
[`error_log()`](http://docs.php.net/manual/en/function.error-log.php) function.
### Send alerts and emails
- _NativeMailerHandler_: Sends emails using PHP's
[`mail()`](http://php.net/manual/en/function.mail.php) function.
- _SwiftMailerHandler_: Sends emails using a [`Swift_Mailer`](http://swiftmailer.org/) instance.
- _PushoverHandler_: Sends mobile notifications via the [Pushover](https://www.pushover.net/) API.
- _HipChatHandler_: Logs records to a [HipChat](http://hipchat.com) chat room using its API.
- _FlowdockHandler_: Logs records to a [Flowdock](https://www.flowdock.com/) account.
- _SlackHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slack API.
- _SlackbotHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slackbot incoming hook.
- _SlackWebhookHandler_: Logs records to a [Slack](https://www.slack.com/) account using Slack Webhooks.
- _MandrillHandler_: Sends emails via the Mandrill API using a [`Swift_Message`](http://swiftmailer.org/) instance.
- _FleepHookHandler_: Logs records to a [Fleep](https://fleep.io/) conversation using Webhooks.
- _IFTTTHandler_: Notifies an [IFTTT](https://ifttt.com/maker) trigger with the log channel, level name and message.
### Log specific servers and networked logging
- _SocketHandler_: Logs records to [sockets](http://php.net/fsockopen), use this
for UNIX and TCP sockets. See an [example](sockets.md).
- _AmqpHandler_: Logs records to an [amqp](http://www.amqp.org/) compatible
server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+).
- _GelfHandler_: Logs records to a [Graylog2](http://www.graylog2.org) server.
- _CubeHandler_: Logs records to a [Cube](http://square.github.com/cube/) server.
- _RavenHandler_: Logs records to a [Sentry](http://getsentry.com/) server using
[raven](https://packagist.org/packages/raven/raven).
- _ZendMonitorHandler_: Logs records to the Zend Monitor present in Zend Server.
- _NewRelicHandler_: Logs records to a [NewRelic](http://newrelic.com/) application.
- _LogglyHandler_: Logs records to a [Loggly](http://www.loggly.com/) account.
- _RollbarHandler_: Logs records to a [Rollbar](https://rollbar.com/) account.
- _SyslogUdpHandler_: Logs records to a remote [Syslogd](http://www.rsyslog.com/) server.
- _LogEntriesHandler_: Logs records to a [LogEntries](http://logentries.com/) account.
### Logging in development
- _FirePHPHandler_: Handler for [FirePHP](http://www.firephp.org/), providing
inline `console` messages within [FireBug](http://getfirebug.com/).
- _ChromePHPHandler_: Handler for [ChromePHP](http://www.chromephp.com/), providing
inline `console` messages within Chrome.
- _BrowserConsoleHandler_: Handler to send logs to browser's Javascript `console` with
no browser extension required. Most browsers supporting `console` API are supported.
- _PHPConsoleHandler_: Handler for [PHP Console](https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef), providing
inline `console` and notification popup messages within Chrome.
### Log to databases
- _RedisHandler_: Logs records to a [redis](http://redis.io) server.
- _MongoDBHandler_: Handler to write records in MongoDB via a
[Mongo](http://pecl.php.net/package/mongo) extension connection.
- _CouchDBHandler_: Logs records to a CouchDB server.
- _DoctrineCouchDBHandler_: Logs records to a CouchDB server via the Doctrine CouchDB ODM.
- _ElasticSearchHandler_: Logs records to an Elastic Search server.
- _DynamoDbHandler_: Logs records to a DynamoDB table with the [AWS SDK](https://github.com/aws/aws-sdk-php).
### Wrappers / Special Handlers
- _FingersCrossedHandler_: A very interesting wrapper. It takes a logger as
parameter and will accumulate log records of all levels until a record
exceeds the defined severity level. At which point it delivers all records,
including those of lower severity, to the handler it wraps. This means that
until an error actually happens you will not see anything in your logs, but
when it happens you will have the full information, including debug and info
records. This provides you with all the information you need, but only when
you need it.
- _DeduplicationHandler_: Useful if you are sending notifications or emails
when critical errors occur. It takes a logger as parameter and will
accumulate log records of all levels until the end of the request (or
`flush()` is called). At that point it delivers all records to the handler
it wraps, but only if the records are unique over a given time period
(60seconds by default). If the records are duplicates they are simply
discarded. The main use of this is in case of critical failure like if your
database is unreachable for example all your requests will fail and that
can result in a lot of notifications being sent. Adding this handler reduces
the amount of notifications to a manageable level.
- _WhatFailureGroupHandler_: This handler extends the _GroupHandler_ ignoring
exceptions raised by each child handler. This allows you to ignore issues
where a remote tcp connection may have died but you do not want your entire
application to crash and may wish to continue to log to other handlers.
- _BufferHandler_: This handler will buffer all the log records it receives
until `close()` is called at which point it will call `handleBatch()` on the
handler it wraps with all the log messages at once. This is very useful to
send an email with all records at once for example instead of having one mail
for every log record.
- _GroupHandler_: This handler groups other handlers. Every record received is
sent to all the handlers it is configured with.
- _FilterHandler_: This handler only lets records of the given levels through
to the wrapped handler.
- _SamplingHandler_: Wraps around another handler and lets you sample records
if you only want to store some of them.
- _NullHandler_: Any record it can handle will be thrown away. This can be used
to put on top of an existing handler stack to disable it temporarily.
- _PsrHandler_: Can be used to forward log records to an existing PSR-3 logger
- _TestHandler_: Used for testing, it records everything that is sent to it and
has accessors to read out the information.
- _HandlerWrapper_: A simple handler wrapper you can inherit from to create
your own wrappers easily.
## Formatters
- _LineFormatter_: Formats a log record into a one-line string.
- _HtmlFormatter_: Used to format log records into a human readable html table, mainly suitable for emails.
- _NormalizerFormatter_: Normalizes objects/resources down to strings so a record can easily be serialized/encoded.
- _ScalarFormatter_: Used to format log records into an associative array of scalar values.
- _JsonFormatter_: Encodes a log record into json.
- _WildfireFormatter_: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler.
- _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler.
- _GelfMessageFormatter_: Used to format log records into Gelf message instances, only useful for the GelfHandler.
- _LogstashFormatter_: Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/latest).
- _ElasticaFormatter_: Used to format log records into an Elastica\Document object, only useful for the ElasticSearchHandler.
- _LogglyFormatter_: Used to format log records into Loggly messages, only useful for the LogglyHandler.
- _FlowdockFormatter_: Used to format log records into Flowdock messages, only useful for the FlowdockHandler.
- _MongoDBFormatter_: Converts \DateTime instances to \MongoDate and objects recursively to arrays, only useful with the MongoDBHandler.
## Processors
- _PsrLogMessageProcessor_: Processes a log record's message according to PSR-3 rules, replacing `{foo}` with the value from `$context['foo']`.
- _IntrospectionProcessor_: Adds the line/file/class/method from which the log call originated.
- _WebProcessor_: Adds the current request URI, request method and client IP to a log record.
- _MemoryUsageProcessor_: Adds the current memory usage to a log record.
- _MemoryPeakUsageProcessor_: Adds the peak memory usage to a log record.
- _ProcessIdProcessor_: Adds the process id to a log record.
- _UidProcessor_: Adds a unique identifier to a log record.
- _GitProcessor_: Adds the current git branch and commit to a log record.
- _TagProcessor_: Adds an array of predefined tags to a log record.
## Third Party Packages
Third party handlers, formatters and processors are
[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You
can also add your own there if you publish one.
&larr; [Usage](01-usage.md) | [Utility classes](03-utilities.md) &rarr;

View File

@ -0,0 +1,13 @@
# Utilities
- _Registry_: The `Monolog\Registry` class lets you configure global loggers that you
can then statically access from anywhere. It is not really a best practice but can
help in some older codebases or for ease of use.
- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register
a Logger instance as an exception handler, error handler or fatal error handler.
- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log
level is reached.
- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain
log level is reached, depending on which channel received the log record.
&larr; [Handlers, Formatters and Processors](02-handlers-formatters-processors.md) | [Extending Monolog](04-extending.md) &rarr;

View File

@ -0,0 +1,76 @@
# Extending Monolog
Monolog is fully extensible, allowing you to adapt your logger to your needs.
## Writing your own handler
Monolog provides many built-in handlers. But if the one you need does not
exist, you can write it and use it in your logger. The only requirement is
to implement `Monolog\Handler\HandlerInterface`.
Let's write a PDOHandler to log records to a database. We will extend the
abstract class provided by Monolog to keep things DRY.
```php
<?php
use Monolog\Logger;
use Monolog\Handler\AbstractProcessingHandler;
class PDOHandler extends AbstractProcessingHandler
{
private $initialized = false;
private $pdo;
private $statement;
public function __construct(PDO $pdo, $level = Logger::DEBUG, $bubble = true)
{
$this->pdo = $pdo;
parent::__construct($level, $bubble);
}
protected function write(array $record)
{
if (!$this->initialized) {
$this->initialize();
}
$this->statement->execute(array(
'channel' => $record['channel'],
'level' => $record['level'],
'message' => $record['formatted'],
'time' => $record['datetime']->format('U'),
));
}
private function initialize()
{
$this->pdo->exec(
'CREATE TABLE IF NOT EXISTS monolog '
.'(channel VARCHAR(255), level INTEGER, message LONGTEXT, time INTEGER UNSIGNED)'
);
$this->statement = $this->pdo->prepare(
'INSERT INTO monolog (channel, level, message, time) VALUES (:channel, :level, :message, :time)'
);
$this->initialized = true;
}
}
```
You can now use this handler in your logger:
```php
<?php
$logger->pushHandler(new PDOHandler(new PDO('sqlite:logs.sqlite')));
// You can now use your logger
$logger->addInfo('My logger is now ready');
```
The `Monolog\Handler\AbstractProcessingHandler` class provides most of the
logic needed for the handler, including the use of processors and the formatting
of the record (which is why we use ``$record['formatted']`` instead of ``$record['message']``).
&larr; [Utility classes](03-utilities.md)

View File

@ -0,0 +1,39 @@
Sockets Handler
===============
This handler allows you to write your logs to sockets using [fsockopen](http://php.net/fsockopen)
or [pfsockopen](http://php.net/pfsockopen).
Persistent sockets are mainly useful in web environments where you gain some performance not closing/opening
the connections between requests.
You can use a `unix://` prefix to access unix sockets and `udp://` to open UDP sockets instead of the default TCP.
Basic Example
-------------
```php
<?php
use Monolog\Logger;
use Monolog\Handler\SocketHandler;
// Create the logger
$logger = new Logger('my_logger');
// Create the handler
$handler = new SocketHandler('unix:///var/log/httpd_app_log.socket');
$handler->setPersistent(true);
// Now add the handler
$logger->pushHandler($handler, Logger::DEBUG);
// You can now use your logger
$logger->addInfo('My logger is now ready');
```
In this example, using syslog-ng, you should see the log on the log server:
cweb1 [2012-02-26 00:12:03] my_logger.INFO: My logger is now ready [] []

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" colors="true">
<testsuites>
<testsuite name="Monolog Test Suite">
<directory>tests/Monolog/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/Monolog/</directory>
</whitelist>
</filter>
<php>
<ini name="date.timezone" value="UTC"/>
</php>
</phpunit>

View File

@ -0,0 +1,230 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Monolog\Handler\AbstractHandler;
/**
* Monolog error handler
*
* A facility to enable logging of runtime errors, exceptions and fatal errors.
*
* Quick setup: <code>ErrorHandler::register($logger);</code>
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class ErrorHandler
{
private $logger;
private $previousExceptionHandler;
private $uncaughtExceptionLevel;
private $previousErrorHandler;
private $errorLevelMap;
private $handleOnlyReportedErrors;
private $hasFatalErrorHandler;
private $fatalLevel;
private $reservedMemory;
private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* Registers a new ErrorHandler for a given Logger
*
* By default it will handle errors, exceptions and fatal errors
*
* @param LoggerInterface $logger
* @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
* @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling
* @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling
* @return ErrorHandler
*/
public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null)
{
//Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929
class_exists('\\Psr\\Log\\LogLevel', true);
$handler = new static($logger);
if ($errorLevelMap !== false) {
$handler->registerErrorHandler($errorLevelMap);
}
if ($exceptionLevel !== false) {
$handler->registerExceptionHandler($exceptionLevel);
}
if ($fatalLevel !== false) {
$handler->registerFatalHandler($fatalLevel);
}
return $handler;
}
public function registerExceptionHandler($level = null, $callPrevious = true)
{
$prev = set_exception_handler(array($this, 'handleException'));
$this->uncaughtExceptionLevel = $level;
if ($callPrevious && $prev) {
$this->previousExceptionHandler = $prev;
}
}
public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true)
{
$prev = set_error_handler(array($this, 'handleError'), $errorTypes);
$this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
if ($callPrevious) {
$this->previousErrorHandler = $prev ?: true;
}
$this->handleOnlyReportedErrors = $handleOnlyReportedErrors;
}
public function registerFatalHandler($level = null, $reservedMemorySize = 20)
{
register_shutdown_function(array($this, 'handleFatalError'));
$this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
$this->fatalLevel = $level;
$this->hasFatalErrorHandler = true;
}
protected function defaultErrorLevelMap()
{
return array(
E_ERROR => LogLevel::CRITICAL,
E_WARNING => LogLevel::WARNING,
E_PARSE => LogLevel::ALERT,
E_NOTICE => LogLevel::NOTICE,
E_CORE_ERROR => LogLevel::CRITICAL,
E_CORE_WARNING => LogLevel::WARNING,
E_COMPILE_ERROR => LogLevel::ALERT,
E_COMPILE_WARNING => LogLevel::WARNING,
E_USER_ERROR => LogLevel::ERROR,
E_USER_WARNING => LogLevel::WARNING,
E_USER_NOTICE => LogLevel::NOTICE,
E_STRICT => LogLevel::NOTICE,
E_RECOVERABLE_ERROR => LogLevel::ERROR,
E_DEPRECATED => LogLevel::NOTICE,
E_USER_DEPRECATED => LogLevel::NOTICE,
);
}
/**
* @private
*/
public function handleException($e)
{
$this->logger->log(
$this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel,
sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()),
array('exception' => $e)
);
if ($this->previousExceptionHandler) {
call_user_func($this->previousExceptionHandler, $e);
}
exit(255);
}
/**
* @private
*/
public function handleError($code, $message, $file = '', $line = 0, $context = array())
{
if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) {
return;
}
// fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries
if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
$level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL;
$this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line));
}
if ($this->previousErrorHandler === true) {
return false;
} elseif ($this->previousErrorHandler) {
return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context);
}
}
/**
* @private
*/
public function handleFatalError()
{
$this->reservedMemory = null;
$lastError = error_get_last();
if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) {
$this->logger->log(
$this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel,
'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'])
);
if ($this->logger instanceof Logger) {
foreach ($this->logger->getHandlers() as $handler) {
if ($handler instanceof AbstractHandler) {
$handler->close();
}
}
}
}
}
private static function codeToString($code)
{
switch ($code) {
case E_ERROR:
return 'E_ERROR';
case E_WARNING:
return 'E_WARNING';
case E_PARSE:
return 'E_PARSE';
case E_NOTICE:
return 'E_NOTICE';
case E_CORE_ERROR:
return 'E_CORE_ERROR';
case E_CORE_WARNING:
return 'E_CORE_WARNING';
case E_COMPILE_ERROR:
return 'E_COMPILE_ERROR';
case E_COMPILE_WARNING:
return 'E_COMPILE_WARNING';
case E_USER_ERROR:
return 'E_USER_ERROR';
case E_USER_WARNING:
return 'E_USER_WARNING';
case E_USER_NOTICE:
return 'E_USER_NOTICE';
case E_STRICT:
return 'E_STRICT';
case E_RECOVERABLE_ERROR:
return 'E_RECOVERABLE_ERROR';
case E_DEPRECATED:
return 'E_DEPRECATED';
case E_USER_DEPRECATED:
return 'E_USER_DEPRECATED';
}
return 'Unknown PHP error';
}
}

View File

@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
/**
* Formats a log message according to the ChromePHP array format
*
* @author Christophe Coevoet <stof@notk.org>
*/
class ChromePHPFormatter implements FormatterInterface
{
/**
* Translates Monolog log levels to Wildfire levels.
*/
private $logLevels = array(
Logger::DEBUG => 'log',
Logger::INFO => 'info',
Logger::NOTICE => 'info',
Logger::WARNING => 'warn',
Logger::ERROR => 'error',
Logger::CRITICAL => 'error',
Logger::ALERT => 'error',
Logger::EMERGENCY => 'error',
);
/**
* {@inheritdoc}
*/
public function format(array $record)
{
// Retrieve the line and file if set and remove them from the formatted extra
$backtrace = 'unknown';
if (isset($record['extra']['file'], $record['extra']['line'])) {
$backtrace = $record['extra']['file'].' : '.$record['extra']['line'];
unset($record['extra']['file'], $record['extra']['line']);
}
$message = array('message' => $record['message']);
if ($record['context']) {
$message['context'] = $record['context'];
}
if ($record['extra']) {
$message['extra'] = $record['extra'];
}
if (count($message) === 1) {
$message = reset($message);
}
return array(
$record['channel'],
$message,
$backtrace,
$this->logLevels[$record['level']],
);
}
public function formatBatch(array $records)
{
$formatted = array();
foreach ($records as $record) {
$formatted[] = $this->format($record);
}
return $formatted;
}
}

View File

@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Elastica\Document;
/**
* Format a log message into an Elastica Document
*
* @author Jelle Vink <jelle.vink@gmail.com>
*/
class ElasticaFormatter extends NormalizerFormatter
{
/**
* @var string Elastic search index name
*/
protected $index;
/**
* @var string Elastic search document type
*/
protected $type;
/**
* @param string $index Elastic Search index name
* @param string $type Elastic Search document type
*/
public function __construct($index, $type)
{
// elasticsearch requires a ISO 8601 format date with optional millisecond precision.
parent::__construct('Y-m-d\TH:i:s.uP');
$this->index = $index;
$this->type = $type;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$record = parent::format($record);
return $this->getDocument($record);
}
/**
* Getter index
* @return string
*/
public function getIndex()
{
return $this->index;
}
/**
* Getter type
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* Convert a log message into an Elastica Document
*
* @param array $record Log message
* @return Document
*/
protected function getDocument($record)
{
$document = new Document();
$document->setData($record);
$document->setType($this->type);
$document->setIndex($this->index);
return $document;
}
}

View File

@ -0,0 +1,116 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* formats the record to be used in the FlowdockHandler
*
* @author Dominik Liebler <liebler.dominik@gmail.com>
*/
class FlowdockFormatter implements FormatterInterface
{
/**
* @var string
*/
private $source;
/**
* @var string
*/
private $sourceEmail;
/**
* @param string $source
* @param string $sourceEmail
*/
public function __construct($source, $sourceEmail)
{
$this->source = $source;
$this->sourceEmail = $sourceEmail;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$tags = array(
'#logs',
'#' . strtolower($record['level_name']),
'#' . $record['channel'],
);
foreach ($record['extra'] as $value) {
$tags[] = '#' . $value;
}
$subject = sprintf(
'in %s: %s - %s',
$this->source,
$record['level_name'],
$this->getShortMessage($record['message'])
);
$record['flowdock'] = array(
'source' => $this->source,
'from_address' => $this->sourceEmail,
'subject' => $subject,
'content' => $record['message'],
'tags' => $tags,
'project' => $this->source,
);
return $record;
}
/**
* {@inheritdoc}
*/
public function formatBatch(array $records)
{
$formatted = array();
foreach ($records as $record) {
$formatted[] = $this->format($record);
}
return $formatted;
}
/**
* @param string $message
*
* @return string
*/
public function getShortMessage($message)
{
static $hasMbString;
if (null === $hasMbString) {
$hasMbString = function_exists('mb_strlen');
}
$maxLength = 45;
if ($hasMbString) {
if (mb_strlen($message, 'UTF-8') > $maxLength) {
$message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...';
}
} else {
if (strlen($message) > $maxLength) {
$message = substr($message, 0, $maxLength - 4) . ' ...';
}
}
return $message;
}
}

View File

@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Class FluentdFormatter
*
* Serializes a log message to Fluentd unix socket protocol
*
* Fluentd config:
*
* <source>
* type unix
* path /var/run/td-agent/td-agent.sock
* </source>
*
* Monolog setup:
*
* $logger = new Monolog\Logger('fluent.tag');
* $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock');
* $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter());
* $logger->pushHandler($fluentHandler);
*
* @author Andrius Putna <fordnox@gmail.com>
*/
class FluentdFormatter implements FormatterInterface
{
/**
* @var bool $levelTag should message level be a part of the fluentd tag
*/
protected $levelTag = false;
public function __construct($levelTag = false)
{
if (!function_exists('json_encode')) {
throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter');
}
$this->levelTag = (bool) $levelTag;
}
public function isUsingLevelsInTag()
{
return $this->levelTag;
}
public function format(array $record)
{
$tag = $record['channel'];
if ($this->levelTag) {
$tag .= '.' . strtolower($record['level_name']);
}
$message = array(
'message' => $record['message'],
'extra' => $record['extra'],
);
if (!$this->levelTag) {
$message['level'] = $record['level'];
$message['level_name'] = $record['level_name'];
}
return json_encode(array($tag, $record['datetime']->getTimestamp(), $message));
}
public function formatBatch(array $records)
{
$message = '';
foreach ($records as $record) {
$message .= $this->format($record);
}
return $message;
}
}

View File

@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Interface for formatters
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface FormatterInterface
{
/**
* Formats a log record.
*
* @param array $record A record to format
* @return mixed The formatted record
*/
public function format(array $record);
/**
* Formats a set of log records.
*
* @param array $records A set of records to format
* @return mixed The formatted set of records
*/
public function formatBatch(array $records);
}

View File

@ -0,0 +1,138 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
use Gelf\Message;
/**
* Serializes a log message to GELF
* @see http://www.graylog2.org/about/gelf
*
* @author Matt Lehner <mlehner@gmail.com>
*/
class GelfMessageFormatter extends NormalizerFormatter
{
const DEFAULT_MAX_LENGTH = 32766;
/**
* @var string the name of the system for the Gelf log message
*/
protected $systemName;
/**
* @var string a prefix for 'extra' fields from the Monolog record (optional)
*/
protected $extraPrefix;
/**
* @var string a prefix for 'context' fields from the Monolog record (optional)
*/
protected $contextPrefix;
/**
* @var int max length per field
*/
protected $maxLength;
/**
* Translates Monolog log levels to Graylog2 log priorities.
*/
private $logLevels = array(
Logger::DEBUG => 7,
Logger::INFO => 6,
Logger::NOTICE => 5,
Logger::WARNING => 4,
Logger::ERROR => 3,
Logger::CRITICAL => 2,
Logger::ALERT => 1,
Logger::EMERGENCY => 0,
);
public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $maxLength = null)
{
parent::__construct('U.u');
$this->systemName = $systemName ?: gethostname();
$this->extraPrefix = $extraPrefix;
$this->contextPrefix = $contextPrefix;
$this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$record = parent::format($record);
if (!isset($record['datetime'], $record['message'], $record['level'])) {
throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given');
}
$message = new Message();
$message
->setTimestamp($record['datetime'])
->setShortMessage((string) $record['message'])
->setHost($this->systemName)
->setLevel($this->logLevels[$record['level']]);
// message length + system name length + 200 for padding / metadata
$len = 200 + strlen((string) $record['message']) + strlen($this->systemName);
if ($len > $this->maxLength) {
$message->setShortMessage(substr($record['message'], 0, $this->maxLength));
}
if (isset($record['channel'])) {
$message->setFacility($record['channel']);
}
if (isset($record['extra']['line'])) {
$message->setLine($record['extra']['line']);
unset($record['extra']['line']);
}
if (isset($record['extra']['file'])) {
$message->setFile($record['extra']['file']);
unset($record['extra']['file']);
}
foreach ($record['extra'] as $key => $val) {
$val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
$len = strlen($this->extraPrefix . $key . $val);
if ($len > $this->maxLength) {
$message->setAdditional($this->extraPrefix . $key, substr($val, 0, $this->maxLength));
break;
}
$message->setAdditional($this->extraPrefix . $key, $val);
}
foreach ($record['context'] as $key => $val) {
$val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
$len = strlen($this->contextPrefix . $key . $val);
if ($len > $this->maxLength) {
$message->setAdditional($this->contextPrefix . $key, substr($val, 0, $this->maxLength));
break;
}
$message->setAdditional($this->contextPrefix . $key, $val);
}
if (null === $message->getFile() && isset($record['context']['exception']['file'])) {
if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) {
$message->setFile($matches[1]);
$message->setLine($matches[2]);
}
}
return $message;
}
}

View File

@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
/**
* Formats incoming records into an HTML table
*
* This is especially useful for html email logging
*
* @author Tiago Brito <tlfbrito@gmail.com>
*/
class HtmlFormatter extends NormalizerFormatter
{
/**
* Translates Monolog log levels to html color priorities.
*/
protected $logLevels = array(
Logger::DEBUG => '#cccccc',
Logger::INFO => '#468847',
Logger::NOTICE => '#3a87ad',
Logger::WARNING => '#c09853',
Logger::ERROR => '#f0ad4e',
Logger::CRITICAL => '#FF7708',
Logger::ALERT => '#C12A19',
Logger::EMERGENCY => '#000000',
);
/**
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
*/
public function __construct($dateFormat = null)
{
parent::__construct($dateFormat);
}
/**
* Creates an HTML table row
*
* @param string $th Row header content
* @param string $td Row standard cell content
* @param bool $escapeTd false if td content must not be html escaped
* @return string
*/
protected function addRow($th, $td = ' ', $escapeTd = true)
{
$th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8');
if ($escapeTd) {
$td = '<pre>'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'</pre>';
}
return "<tr style=\"padding: 4px;spacing: 0;text-align: left;\">\n<th style=\"background: #cccccc\" width=\"100px\">$th:</th>\n<td style=\"padding: 4px;spacing: 0;text-align: left;background: #eeeeee\">".$td."</td>\n</tr>";
}
/**
* Create a HTML h1 tag
*
* @param string $title Text to be in the h1
* @param int $level Error level
* @return string
*/
protected function addTitle($title, $level)
{
$title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8');
return '<h1 style="background: '.$this->logLevels[$level].';color: #ffffff;padding: 5px;" class="monolog-output">'.$title.'</h1>';
}
/**
* Formats a log record.
*
* @param array $record A record to format
* @return mixed The formatted record
*/
public function format(array $record)
{
$output = $this->addTitle($record['level_name'], $record['level']);
$output .= '<table cellspacing="1" width="100%" class="monolog-output">';
$output .= $this->addRow('Message', (string) $record['message']);
$output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat));
$output .= $this->addRow('Channel', $record['channel']);
if ($record['context']) {
$embeddedTable = '<table cellspacing="1" width="100%">';
foreach ($record['context'] as $key => $value) {
$embeddedTable .= $this->addRow($key, $this->convertToString($value));
}
$embeddedTable .= '</table>';
$output .= $this->addRow('Context', $embeddedTable, false);
}
if ($record['extra']) {
$embeddedTable = '<table cellspacing="1" width="100%">';
foreach ($record['extra'] as $key => $value) {
$embeddedTable .= $this->addRow($key, $this->convertToString($value));
}
$embeddedTable .= '</table>';
$output .= $this->addRow('Extra', $embeddedTable, false);
}
return $output.'</table>';
}
/**
* Formats a set of log records.
*
* @param array $records A set of records to format
* @return mixed The formatted set of records
*/
public function formatBatch(array $records)
{
$message = '';
foreach ($records as $record) {
$message .= $this->format($record);
}
return $message;
}
protected function convertToString($data)
{
if (null === $data || is_scalar($data)) {
return (string) $data;
}
$data = $this->normalize($data);
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return str_replace('\\/', '/', json_encode($data));
}
}

View File

@ -0,0 +1,208 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Exception;
use Throwable;
/**
* Encodes whatever record data is passed to it as json
*
* This can be useful to log to databases or remote APIs
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class JsonFormatter extends NormalizerFormatter
{
const BATCH_MODE_JSON = 1;
const BATCH_MODE_NEWLINES = 2;
protected $batchMode;
protected $appendNewline;
/**
* @var bool
*/
protected $includeStacktraces = false;
/**
* @param int $batchMode
* @param bool $appendNewline
*/
public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true)
{
$this->batchMode = $batchMode;
$this->appendNewline = $appendNewline;
}
/**
* The batch mode option configures the formatting style for
* multiple records. By default, multiple records will be
* formatted as a JSON-encoded array. However, for
* compatibility with some API endpoints, alternative styles
* are available.
*
* @return int
*/
public function getBatchMode()
{
return $this->batchMode;
}
/**
* True if newlines are appended to every formatted record
*
* @return bool
*/
public function isAppendingNewlines()
{
return $this->appendNewline;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : '');
}
/**
* {@inheritdoc}
*/
public function formatBatch(array $records)
{
switch ($this->batchMode) {
case static::BATCH_MODE_NEWLINES:
return $this->formatBatchNewlines($records);
case static::BATCH_MODE_JSON:
default:
return $this->formatBatchJson($records);
}
}
/**
* @param bool $include
*/
public function includeStacktraces($include = true)
{
$this->includeStacktraces = $include;
}
/**
* Return a JSON-encoded array of records.
*
* @param array $records
* @return string
*/
protected function formatBatchJson(array $records)
{
return $this->toJson($this->normalize($records), true);
}
/**
* Use new lines to separate records instead of a
* JSON-encoded array.
*
* @param array $records
* @return string
*/
protected function formatBatchNewlines(array $records)
{
$instance = $this;
$oldNewline = $this->appendNewline;
$this->appendNewline = false;
array_walk($records, function (&$value, $key) use ($instance) {
$value = $instance->format($value);
});
$this->appendNewline = $oldNewline;
return implode("\n", $records);
}
/**
* Normalizes given $data.
*
* @param mixed $data
*
* @return mixed
*/
protected function normalize($data)
{
if (is_array($data) || $data instanceof \Traversable) {
$normalized = array();
$count = 1;
foreach ($data as $key => $value) {
if ($count++ >= 1000) {
$normalized['...'] = 'Over 1000 items, aborting normalization';
break;
}
$normalized[$key] = $this->normalize($value);
}
return $normalized;
}
if ($data instanceof Exception || $data instanceof Throwable) {
return $this->normalizeException($data);
}
return $data;
}
/**
* Normalizes given exception with or without its own stack trace based on
* `includeStacktraces` property.
*
* @param Exception|Throwable $e
*
* @return array
*/
protected function normalizeException($e)
{
// TODO 2.0 only check for Throwable
if (!$e instanceof Exception && !$e instanceof Throwable) {
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
}
$data = array(
'class' => get_class($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile().':'.$e->getLine(),
);
if ($this->includeStacktraces) {
$trace = $e->getTrace();
foreach ($trace as $frame) {
if (isset($frame['file'])) {
$data['trace'][] = $frame['file'].':'.$frame['line'];
} elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
// We should again normalize the frames, because it might contain invalid items
$data['trace'][] = $frame['function'];
} else {
// We should again normalize the frames, because it might contain invalid items
$data['trace'][] = $this->normalize($frame);
}
}
}
if ($previous = $e->getPrevious()) {
$data['previous'] = $this->normalizeException($previous);
}
return $data;
}
}

View File

@ -0,0 +1,179 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Formats incoming records into a one-line string
*
* This is especially useful for logging to files
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Christophe Coevoet <stof@notk.org>
*/
class LineFormatter extends NormalizerFormatter
{
const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
protected $format;
protected $allowInlineLineBreaks;
protected $ignoreEmptyContextAndExtra;
protected $includeStacktraces;
/**
* @param string $format The format of the message
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
* @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
* @param bool $ignoreEmptyContextAndExtra
*/
public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false)
{
$this->format = $format ?: static::SIMPLE_FORMAT;
$this->allowInlineLineBreaks = $allowInlineLineBreaks;
$this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
parent::__construct($dateFormat);
}
public function includeStacktraces($include = true)
{
$this->includeStacktraces = $include;
if ($this->includeStacktraces) {
$this->allowInlineLineBreaks = true;
}
}
public function allowInlineLineBreaks($allow = true)
{
$this->allowInlineLineBreaks = $allow;
}
public function ignoreEmptyContextAndExtra($ignore = true)
{
$this->ignoreEmptyContextAndExtra = $ignore;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$vars = parent::format($record);
$output = $this->format;
foreach ($vars['extra'] as $var => $val) {
if (false !== strpos($output, '%extra.'.$var.'%')) {
$output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);
unset($vars['extra'][$var]);
}
}
foreach ($vars['context'] as $var => $val) {
if (false !== strpos($output, '%context.'.$var.'%')) {
$output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);
unset($vars['context'][$var]);
}
}
if ($this->ignoreEmptyContextAndExtra) {
if (empty($vars['context'])) {
unset($vars['context']);
$output = str_replace('%context%', '', $output);
}
if (empty($vars['extra'])) {
unset($vars['extra']);
$output = str_replace('%extra%', '', $output);
}
}
foreach ($vars as $var => $val) {
if (false !== strpos($output, '%'.$var.'%')) {
$output = str_replace('%'.$var.'%', $this->stringify($val), $output);
}
}
// remove leftover %extra.xxx% and %context.xxx% if any
if (false !== strpos($output, '%')) {
$output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
}
return $output;
}
public function formatBatch(array $records)
{
$message = '';
foreach ($records as $record) {
$message .= $this->format($record);
}
return $message;
}
public function stringify($value)
{
return $this->replaceNewlines($this->convertToString($value));
}
protected function normalizeException($e)
{
// TODO 2.0 only check for Throwable
if (!$e instanceof \Exception && !$e instanceof \Throwable) {
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
}
$previousText = '';
if ($previous = $e->getPrevious()) {
do {
$previousText .= ', '.get_class($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine();
} while ($previous = $previous->getPrevious());
}
$str = '[object] ('.get_class($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')';
if ($this->includeStacktraces) {
$str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n";
}
return $str;
}
protected function convertToString($data)
{
if (null === $data || is_bool($data)) {
return var_export($data, true);
}
if (is_scalar($data)) {
return (string) $data;
}
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return $this->toJson($data, true);
}
return str_replace('\\/', '/', @json_encode($data));
}
protected function replaceNewlines($str)
{
if ($this->allowInlineLineBreaks) {
if (0 === strpos($str, '{')) {
return str_replace(array('\r', '\n'), array("\r", "\n"), $str);
}
return $str;
}
return str_replace(array("\r\n", "\r", "\n"), ' ', $str);
}
}

View File

@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Encodes message information into JSON in a format compatible with Loggly.
*
* @author Adam Pancutt <adam@pancutt.com>
*/
class LogglyFormatter extends JsonFormatter
{
/**
* Overrides the default batch mode to new lines for compatibility with the
* Loggly bulk API.
*
* @param int $batchMode
*/
public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false)
{
parent::__construct($batchMode, $appendNewline);
}
/**
* Appends the 'timestamp' parameter for indexing by Loggly.
*
* @see https://www.loggly.com/docs/automated-parsing/#json
* @see \Monolog\Formatter\JsonFormatter::format()
*/
public function format(array $record)
{
if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) {
$record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO");
// TODO 2.0 unset the 'datetime' parameter, retained for BC
}
return parent::format($record);
}
}

View File

@ -0,0 +1,166 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Serializes a log message to Logstash Event Format
*
* @see http://logstash.net/
* @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb
*
* @author Tim Mower <timothy.mower@gmail.com>
*/
class LogstashFormatter extends NormalizerFormatter
{
const V0 = 0;
const V1 = 1;
/**
* @var string the name of the system for the Logstash log message, used to fill the @source field
*/
protected $systemName;
/**
* @var string an application name for the Logstash log message, used to fill the @type field
*/
protected $applicationName;
/**
* @var string a prefix for 'extra' fields from the Monolog record (optional)
*/
protected $extraPrefix;
/**
* @var string a prefix for 'context' fields from the Monolog record (optional)
*/
protected $contextPrefix;
/**
* @var int logstash format version to use
*/
protected $version;
/**
* @param string $applicationName the application that sends the data, used as the "type" field of logstash
* @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine
* @param string $extraPrefix prefix for extra keys inside logstash "fields"
* @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_
* @param int $version the logstash format version to use, defaults to 0
*/
public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0)
{
// logstash requires a ISO 8601 format date with optional millisecond precision.
parent::__construct('Y-m-d\TH:i:s.uP');
$this->systemName = $systemName ?: gethostname();
$this->applicationName = $applicationName;
$this->extraPrefix = $extraPrefix;
$this->contextPrefix = $contextPrefix;
$this->version = $version;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$record = parent::format($record);
if ($this->version === self::V1) {
$message = $this->formatV1($record);
} else {
$message = $this->formatV0($record);
}
return $this->toJson($message) . "\n";
}
protected function formatV0(array $record)
{
if (empty($record['datetime'])) {
$record['datetime'] = gmdate('c');
}
$message = array(
'@timestamp' => $record['datetime'],
'@source' => $this->systemName,
'@fields' => array(),
);
if (isset($record['message'])) {
$message['@message'] = $record['message'];
}
if (isset($record['channel'])) {
$message['@tags'] = array($record['channel']);
$message['@fields']['channel'] = $record['channel'];
}
if (isset($record['level'])) {
$message['@fields']['level'] = $record['level'];
}
if ($this->applicationName) {
$message['@type'] = $this->applicationName;
}
if (isset($record['extra']['server'])) {
$message['@source_host'] = $record['extra']['server'];
}
if (isset($record['extra']['url'])) {
$message['@source_path'] = $record['extra']['url'];
}
if (!empty($record['extra'])) {
foreach ($record['extra'] as $key => $val) {
$message['@fields'][$this->extraPrefix . $key] = $val;
}
}
if (!empty($record['context'])) {
foreach ($record['context'] as $key => $val) {
$message['@fields'][$this->contextPrefix . $key] = $val;
}
}
return $message;
}
protected function formatV1(array $record)
{
if (empty($record['datetime'])) {
$record['datetime'] = gmdate('c');
}
$message = array(
'@timestamp' => $record['datetime'],
'@version' => 1,
'host' => $this->systemName,
);
if (isset($record['message'])) {
$message['message'] = $record['message'];
}
if (isset($record['channel'])) {
$message['type'] = $record['channel'];
$message['channel'] = $record['channel'];
}
if (isset($record['level_name'])) {
$message['level'] = $record['level_name'];
}
if ($this->applicationName) {
$message['type'] = $this->applicationName;
}
if (!empty($record['extra'])) {
foreach ($record['extra'] as $key => $val) {
$message[$this->extraPrefix . $key] = $val;
}
}
if (!empty($record['context'])) {
foreach ($record['context'] as $key => $val) {
$message[$this->contextPrefix . $key] = $val;
}
}
return $message;
}
}

View File

@ -0,0 +1,105 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Formats a record for use with the MongoDBHandler.
*
* @author Florian Plattner <me@florianplattner.de>
*/
class MongoDBFormatter implements FormatterInterface
{
private $exceptionTraceAsString;
private $maxNestingLevel;
/**
* @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2
* @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings
*/
public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true)
{
$this->maxNestingLevel = max($maxNestingLevel, 0);
$this->exceptionTraceAsString = (bool) $exceptionTraceAsString;
}
/**
* {@inheritDoc}
*/
public function format(array $record)
{
return $this->formatArray($record);
}
/**
* {@inheritDoc}
*/
public function formatBatch(array $records)
{
foreach ($records as $key => $record) {
$records[$key] = $this->format($record);
}
return $records;
}
protected function formatArray(array $record, $nestingLevel = 0)
{
if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) {
foreach ($record as $name => $value) {
if ($value instanceof \DateTime) {
$record[$name] = $this->formatDate($value, $nestingLevel + 1);
} elseif ($value instanceof \Exception) {
$record[$name] = $this->formatException($value, $nestingLevel + 1);
} elseif (is_array($value)) {
$record[$name] = $this->formatArray($value, $nestingLevel + 1);
} elseif (is_object($value)) {
$record[$name] = $this->formatObject($value, $nestingLevel + 1);
}
}
} else {
$record = '[...]';
}
return $record;
}
protected function formatObject($value, $nestingLevel)
{
$objectVars = get_object_vars($value);
$objectVars['class'] = get_class($value);
return $this->formatArray($objectVars, $nestingLevel);
}
protected function formatException(\Exception $exception, $nestingLevel)
{
$formattedException = array(
'class' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile() . ':' . $exception->getLine(),
);
if ($this->exceptionTraceAsString === true) {
$formattedException['trace'] = $exception->getTraceAsString();
} else {
$formattedException['trace'] = $exception->getTrace();
}
return $this->formatArray($formattedException, $nestingLevel);
}
protected function formatDate(\DateTime $value, $nestingLevel)
{
return new \MongoDate($value->getTimestamp());
}
}

View File

@ -0,0 +1,297 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Exception;
/**
* Normalizes incoming records to remove objects/resources so it's easier to dump to various targets
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class NormalizerFormatter implements FormatterInterface
{
const SIMPLE_DATE = "Y-m-d H:i:s";
protected $dateFormat;
/**
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
*/
public function __construct($dateFormat = null)
{
$this->dateFormat = $dateFormat ?: static::SIMPLE_DATE;
if (!function_exists('json_encode')) {
throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter');
}
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
return $this->normalize($record);
}
/**
* {@inheritdoc}
*/
public function formatBatch(array $records)
{
foreach ($records as $key => $record) {
$records[$key] = $this->format($record);
}
return $records;
}
protected function normalize($data)
{
if (null === $data || is_scalar($data)) {
if (is_float($data)) {
if (is_infinite($data)) {
return ($data > 0 ? '' : '-') . 'INF';
}
if (is_nan($data)) {
return 'NaN';
}
}
return $data;
}
if (is_array($data)) {
$normalized = array();
$count = 1;
foreach ($data as $key => $value) {
if ($count++ >= 1000) {
$normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
break;
}
$normalized[$key] = $this->normalize($value);
}
return $normalized;
}
if ($data instanceof \DateTime) {
return $data->format($this->dateFormat);
}
if (is_object($data)) {
// TODO 2.0 only check for Throwable
if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) {
return $this->normalizeException($data);
}
// non-serializable objects that implement __toString stringified
if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) {
$value = $data->__toString();
} else {
// the rest is json-serialized in some way
$value = $this->toJson($data, true);
}
return sprintf("[object] (%s: %s)", get_class($data), $value);
}
if (is_resource($data)) {
return sprintf('[resource] (%s)', get_resource_type($data));
}
return '[unknown('.gettype($data).')]';
}
protected function normalizeException($e)
{
// TODO 2.0 only check for Throwable
if (!$e instanceof Exception && !$e instanceof \Throwable) {
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
}
$data = array(
'class' => get_class($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile().':'.$e->getLine(),
);
if ($e instanceof \SoapFault) {
if (isset($e->faultcode)) {
$data['faultcode'] = $e->faultcode;
}
if (isset($e->faultactor)) {
$data['faultactor'] = $e->faultactor;
}
if (isset($e->detail)) {
$data['detail'] = $e->detail;
}
}
$trace = $e->getTrace();
foreach ($trace as $frame) {
if (isset($frame['file'])) {
$data['trace'][] = $frame['file'].':'.$frame['line'];
} elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
// We should again normalize the frames, because it might contain invalid items
$data['trace'][] = $frame['function'];
} else {
// We should again normalize the frames, because it might contain invalid items
$data['trace'][] = $this->toJson($this->normalize($frame), true);
}
}
if ($previous = $e->getPrevious()) {
$data['previous'] = $this->normalizeException($previous);
}
return $data;
}
/**
* Return the JSON representation of a value
*
* @param mixed $data
* @param bool $ignoreErrors
* @throws \RuntimeException if encoding fails and errors are not ignored
* @return string
*/
protected function toJson($data, $ignoreErrors = false)
{
// suppress json_encode errors since it's twitchy with some inputs
if ($ignoreErrors) {
return @$this->jsonEncode($data);
}
$json = $this->jsonEncode($data);
if ($json === false) {
$json = $this->handleJsonError(json_last_error(), $data);
}
return $json;
}
/**
* @param mixed $data
* @return string JSON encoded data or null on failure
*/
private function jsonEncode($data)
{
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return json_encode($data);
}
/**
* Handle a json_encode failure.
*
* If the failure is due to invalid string encoding, try to clean the
* input and encode again. If the second encoding attempt fails, the
* inital error is not encoding related or the input can't be cleaned then
* raise a descriptive exception.
*
* @param int $code return code of json_last_error function
* @param mixed $data data that was meant to be encoded
* @throws \RuntimeException if failure can't be corrected
* @return string JSON encoded data after error correction
*/
private function handleJsonError($code, $data)
{
if ($code !== JSON_ERROR_UTF8) {
$this->throwEncodeError($code, $data);
}
if (is_string($data)) {
$this->detectAndCleanUtf8($data);
} elseif (is_array($data)) {
array_walk_recursive($data, array($this, 'detectAndCleanUtf8'));
} else {
$this->throwEncodeError($code, $data);
}
$json = $this->jsonEncode($data);
if ($json === false) {
$this->throwEncodeError(json_last_error(), $data);
}
return $json;
}
/**
* Throws an exception according to a given code with a customized message
*
* @param int $code return code of json_last_error function
* @param mixed $data data that was meant to be encoded
* @throws \RuntimeException
*/
private function throwEncodeError($code, $data)
{
switch ($code) {
case JSON_ERROR_DEPTH:
$msg = 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Unexpected control character found';
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$msg = 'Unknown error';
}
throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true));
}
/**
* Detect invalid UTF-8 string characters and convert to valid UTF-8.
*
* Valid UTF-8 input will be left unmodified, but strings containing
* invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed
* original encoding of ISO-8859-15. This conversion may result in
* incorrect output if the actual encoding was not ISO-8859-15, but it
* will be clean UTF-8 output and will not rely on expensive and fragile
* detection algorithms.
*
* Function converts the input in place in the passed variable so that it
* can be used as a callback for array_walk_recursive.
*
* @param mixed &$data Input to check and convert if needed
* @private
*/
public function detectAndCleanUtf8(&$data)
{
if (is_string($data) && !preg_match('//u', $data)) {
$data = preg_replace_callback(
'/[\x80-\xFF]+/',
function ($m) { return utf8_encode($m[0]); },
$data
);
$data = str_replace(
array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'),
array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'),
$data
);
}
}
}

View File

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Formats data into an associative array of scalar values.
* Objects and arrays will be JSON encoded.
*
* @author Andrew Lawson <adlawson@gmail.com>
*/
class ScalarFormatter extends NormalizerFormatter
{
/**
* {@inheritdoc}
*/
public function format(array $record)
{
foreach ($record as $key => $value) {
$record[$key] = $this->normalizeValue($value);
}
return $record;
}
/**
* @param mixed $value
* @return mixed
*/
protected function normalizeValue($value)
{
$normalized = $this->normalize($value);
if (is_array($normalized) || is_object($normalized)) {
return $this->toJson($normalized, true);
}
return $normalized;
}
}

View File

@ -0,0 +1,113 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
/**
* Serializes a log message according to Wildfire's header requirements
*
* @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
* @author Christophe Coevoet <stof@notk.org>
* @author Kirill chEbba Chebunin <iam@chebba.org>
*/
class WildfireFormatter extends NormalizerFormatter
{
const TABLE = 'table';
/**
* Translates Monolog log levels to Wildfire levels.
*/
private $logLevels = array(
Logger::DEBUG => 'LOG',
Logger::INFO => 'INFO',
Logger::NOTICE => 'INFO',
Logger::WARNING => 'WARN',
Logger::ERROR => 'ERROR',
Logger::CRITICAL => 'ERROR',
Logger::ALERT => 'ERROR',
Logger::EMERGENCY => 'ERROR',
);
/**
* {@inheritdoc}
*/
public function format(array $record)
{
// Retrieve the line and file if set and remove them from the formatted extra
$file = $line = '';
if (isset($record['extra']['file'])) {
$file = $record['extra']['file'];
unset($record['extra']['file']);
}
if (isset($record['extra']['line'])) {
$line = $record['extra']['line'];
unset($record['extra']['line']);
}
$record = $this->normalize($record);
$message = array('message' => $record['message']);
$handleError = false;
if ($record['context']) {
$message['context'] = $record['context'];
$handleError = true;
}
if ($record['extra']) {
$message['extra'] = $record['extra'];
$handleError = true;
}
if (count($message) === 1) {
$message = reset($message);
}
if (isset($record['context'][self::TABLE])) {
$type = 'TABLE';
$label = $record['channel'] .': '. $record['message'];
$message = $record['context'][self::TABLE];
} else {
$type = $this->logLevels[$record['level']];
$label = $record['channel'];
}
// Create JSON object describing the appearance of the message in the console
$json = $this->toJson(array(
array(
'Type' => $type,
'File' => $file,
'Line' => $line,
'Label' => $label,
),
$message,
), $handleError);
// The message itself is a serialization of the above JSON object + it's length
return sprintf(
'%s|%s|',
strlen($json),
$json
);
}
public function formatBatch(array $records)
{
throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter');
}
protected function normalize($data)
{
if (is_object($data) && !$data instanceof \DateTime) {
return $data;
}
return parent::normalize($data);
}
}

View File

@ -0,0 +1,186 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
/**
* Base Handler class providing the Handler structure
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
abstract class AbstractHandler implements HandlerInterface
{
protected $level = Logger::DEBUG;
protected $bubble = true;
/**
* @var FormatterInterface
*/
protected $formatter;
protected $processors = array();
/**
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
{
$this->setLevel($level);
$this->bubble = $bubble;
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
return $record['level'] >= $this->level;
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
foreach ($records as $record) {
$this->handle($record);
}
}
/**
* Closes the handler.
*
* This will be called automatically when the object is destroyed
*/
public function close()
{
}
/**
* {@inheritdoc}
*/
public function pushProcessor($callback)
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given');
}
array_unshift($this->processors, $callback);
return $this;
}
/**
* {@inheritdoc}
*/
public function popProcessor()
{
if (!$this->processors) {
throw new \LogicException('You tried to pop from an empty processor stack.');
}
return array_shift($this->processors);
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
$this->formatter = $formatter;
return $this;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
if (!$this->formatter) {
$this->formatter = $this->getDefaultFormatter();
}
return $this->formatter;
}
/**
* Sets minimum logging level at which this handler will be triggered.
*
* @param int|string $level Level or level name
* @return self
*/
public function setLevel($level)
{
$this->level = Logger::toMonologLevel($level);
return $this;
}
/**
* Gets minimum logging level at which this handler will be triggered.
*
* @return int
*/
public function getLevel()
{
return $this->level;
}
/**
* Sets the bubbling behavior.
*
* @param Boolean $bubble true means that this handler allows bubbling.
* false means that bubbling is not permitted.
* @return self
*/
public function setBubble($bubble)
{
$this->bubble = $bubble;
return $this;
}
/**
* Gets the bubbling behavior.
*
* @return Boolean true means that this handler allows bubbling.
* false means that bubbling is not permitted.
*/
public function getBubble()
{
return $this->bubble;
}
public function __destruct()
{
try {
$this->close();
} catch (\Exception $e) {
// do nothing
} catch (\Throwable $e) {
// do nothing
}
}
/**
* Gets the default formatter.
*
* @return FormatterInterface
*/
protected function getDefaultFormatter()
{
return new LineFormatter();
}
}

View File

@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
/**
* Base Handler class providing the Handler structure
*
* Classes extending it should (in most cases) only implement write($record)
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Christophe Coevoet <stof@notk.org>
*/
abstract class AbstractProcessingHandler extends AbstractHandler
{
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if (!$this->isHandling($record)) {
return false;
}
$record = $this->processRecord($record);
$record['formatted'] = $this->getFormatter()->format($record);
$this->write($record);
return false === $this->bubble;
}
/**
* Writes the record down to the log of the implementing handler
*
* @param array $record
* @return void
*/
abstract protected function write(array $record);
/**
* Processes a record.
*
* @param array $record
* @return array
*/
protected function processRecord(array $record)
{
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
return $record;
}
}

View File

@ -0,0 +1,101 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
/**
* Common syslog functionality
*/
abstract class AbstractSyslogHandler extends AbstractProcessingHandler
{
protected $facility;
/**
* Translates Monolog log levels to syslog log priorities.
*/
protected $logLevels = array(
Logger::DEBUG => LOG_DEBUG,
Logger::INFO => LOG_INFO,
Logger::NOTICE => LOG_NOTICE,
Logger::WARNING => LOG_WARNING,
Logger::ERROR => LOG_ERR,
Logger::CRITICAL => LOG_CRIT,
Logger::ALERT => LOG_ALERT,
Logger::EMERGENCY => LOG_EMERG,
);
/**
* List of valid log facility names.
*/
protected $facilities = array(
'auth' => LOG_AUTH,
'authpriv' => LOG_AUTHPRIV,
'cron' => LOG_CRON,
'daemon' => LOG_DAEMON,
'kern' => LOG_KERN,
'lpr' => LOG_LPR,
'mail' => LOG_MAIL,
'news' => LOG_NEWS,
'syslog' => LOG_SYSLOG,
'user' => LOG_USER,
'uucp' => LOG_UUCP,
);
/**
* @param mixed $facility
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->facilities['local0'] = LOG_LOCAL0;
$this->facilities['local1'] = LOG_LOCAL1;
$this->facilities['local2'] = LOG_LOCAL2;
$this->facilities['local3'] = LOG_LOCAL3;
$this->facilities['local4'] = LOG_LOCAL4;
$this->facilities['local5'] = LOG_LOCAL5;
$this->facilities['local6'] = LOG_LOCAL6;
$this->facilities['local7'] = LOG_LOCAL7;
} else {
$this->facilities['local0'] = 128; // LOG_LOCAL0
$this->facilities['local1'] = 136; // LOG_LOCAL1
$this->facilities['local2'] = 144; // LOG_LOCAL2
$this->facilities['local3'] = 152; // LOG_LOCAL3
$this->facilities['local4'] = 160; // LOG_LOCAL4
$this->facilities['local5'] = 168; // LOG_LOCAL5
$this->facilities['local6'] = 176; // LOG_LOCAL6
$this->facilities['local7'] = 184; // LOG_LOCAL7
}
// convert textual description of facility to syslog constant
if (array_key_exists(strtolower($facility), $this->facilities)) {
$facility = $this->facilities[strtolower($facility)];
} elseif (!in_array($facility, array_values($this->facilities), true)) {
throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given');
}
$this->facility = $facility;
}
/**
* {@inheritdoc}
*/
protected function getDefaultFormatter()
{
return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%');
}
}

View File

@ -0,0 +1,148 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\JsonFormatter;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Channel\AMQPChannel;
use AMQPExchange;
class AmqpHandler extends AbstractProcessingHandler
{
/**
* @var AMQPExchange|AMQPChannel $exchange
*/
protected $exchange;
/**
* @var string
*/
protected $exchangeName;
/**
* @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use
* @param string $exchangeName
* @param int $level
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true)
{
if ($exchange instanceof AMQPExchange) {
$exchange->setName($exchangeName);
} elseif ($exchange instanceof AMQPChannel) {
$this->exchangeName = $exchangeName;
} else {
throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required');
}
$this->exchange = $exchange;
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$data = $record["formatted"];
$routingKey = $this->getRoutingKey($record);
if ($this->exchange instanceof AMQPExchange) {
$this->exchange->publish(
$data,
$routingKey,
0,
array(
'delivery_mode' => 2,
'content_type' => 'application/json',
)
);
} else {
$this->exchange->basic_publish(
$this->createAmqpMessage($data),
$this->exchangeName,
$routingKey
);
}
}
/**
* {@inheritDoc}
*/
public function handleBatch(array $records)
{
if ($this->exchange instanceof AMQPExchange) {
parent::handleBatch($records);
return;
}
foreach ($records as $record) {
if (!$this->isHandling($record)) {
continue;
}
$record = $this->processRecord($record);
$data = $this->getFormatter()->format($record);
$this->exchange->batch_basic_publish(
$this->createAmqpMessage($data),
$this->exchangeName,
$this->getRoutingKey($record)
);
}
$this->exchange->publish_batch();
}
/**
* Gets the routing key for the AMQP exchange
*
* @param array $record
* @return string
*/
protected function getRoutingKey(array $record)
{
$routingKey = sprintf(
'%s.%s',
// TODO 2.0 remove substr call
substr($record['level_name'], 0, 4),
$record['channel']
);
return strtolower($routingKey);
}
/**
* @param string $data
* @return AMQPMessage
*/
private function createAmqpMessage($data)
{
return new AMQPMessage(
(string) $data,
array(
'delivery_mode' => 2,
'content_type' => 'application/json',
)
);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
}
}

View File

@ -0,0 +1,230 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\LineFormatter;
/**
* Handler sending logs to browser's javascript console with no browser extension required
*
* @author Olivier Poitrey <rs@dailymotion.com>
*/
class BrowserConsoleHandler extends AbstractProcessingHandler
{
protected static $initialized = false;
protected static $records = array();
/**
* {@inheritDoc}
*
* Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format.
*
* Example of formatted string:
*
* You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white}
*/
protected function getDefaultFormatter()
{
return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%');
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
// Accumulate records
self::$records[] = $record;
// Register shutdown handler if not already done
if (!self::$initialized) {
self::$initialized = true;
$this->registerShutdownFunction();
}
}
/**
* Convert records to javascript console commands and send it to the browser.
* This method is automatically called on PHP shutdown if output is HTML or Javascript.
*/
public static function send()
{
$format = self::getResponseFormat();
if ($format === 'unknown') {
return;
}
if (count(self::$records)) {
if ($format === 'html') {
self::writeOutput('<script>' . self::generateScript() . '</script>');
} elseif ($format === 'js') {
self::writeOutput(self::generateScript());
}
self::reset();
}
}
/**
* Forget all logged records
*/
public static function reset()
{
self::$records = array();
}
/**
* Wrapper for register_shutdown_function to allow overriding
*/
protected function registerShutdownFunction()
{
if (PHP_SAPI !== 'cli') {
register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send'));
}
}
/**
* Wrapper for echo to allow overriding
*
* @param string $str
*/
protected static function writeOutput($str)
{
echo $str;
}
/**
* Checks the format of the response
*
* If Content-Type is set to application/javascript or text/javascript -> js
* If Content-Type is set to text/html, or is unset -> html
* If Content-Type is anything else -> unknown
*
* @return string One of 'js', 'html' or 'unknown'
*/
protected static function getResponseFormat()
{
// Check content type
foreach (headers_list() as $header) {
if (stripos($header, 'content-type:') === 0) {
// This handler only works with HTML and javascript outputs
// text/javascript is obsolete in favour of application/javascript, but still used
if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) {
return 'js';
}
if (stripos($header, 'text/html') === false) {
return 'unknown';
}
break;
}
}
return 'html';
}
private static function generateScript()
{
$script = array();
foreach (self::$records as $record) {
$context = self::dump('Context', $record['context']);
$extra = self::dump('Extra', $record['extra']);
if (empty($context) && empty($extra)) {
$script[] = self::call_array('log', self::handleStyles($record['formatted']));
} else {
$script = array_merge($script,
array(self::call_array('groupCollapsed', self::handleStyles($record['formatted']))),
$context,
$extra,
array(self::call('groupEnd'))
);
}
}
return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);";
}
private static function handleStyles($formatted)
{
$args = array(self::quote('font-weight: normal'));
$format = '%c' . $formatted;
preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
foreach (array_reverse($matches) as $match) {
$args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0]));
$args[] = '"font-weight: normal"';
$pos = $match[0][1];
$format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0]));
}
array_unshift($args, self::quote($format));
return $args;
}
private static function handleCustomStyles($style, $string)
{
static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey');
static $labels = array();
return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) {
if (trim($m[1]) === 'autolabel') {
// Format the string as a label with consistent auto assigned background color
if (!isset($labels[$string])) {
$labels[$string] = $colors[count($labels) % count($colors)];
}
$color = $labels[$string];
return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px";
}
return $m[1];
}, $style);
}
private static function dump($title, array $dict)
{
$script = array();
$dict = array_filter($dict);
if (empty($dict)) {
return $script;
}
$script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title));
foreach ($dict as $key => $value) {
$value = json_encode($value);
if (empty($value)) {
$value = self::quote('');
}
$script[] = self::call('log', self::quote('%s: %o'), self::quote($key), $value);
}
return $script;
}
private static function quote($arg)
{
return '"' . addcslashes($arg, "\"\n\\") . '"';
}
private static function call()
{
$args = func_get_args();
$method = array_shift($args);
return self::call_array($method, $args);
}
private static function call_array($method, array $args)
{
return 'c.' . $method . '(' . implode(', ', $args) . ');';
}
}

View File

@ -0,0 +1,117 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Buffers all records until closing the handler and then pass them as batch.
*
* This is useful for a MailHandler to send only one mail per request instead of
* sending one per log message.
*
* @author Christophe Coevoet <stof@notk.org>
*/
class BufferHandler extends AbstractHandler
{
protected $handler;
protected $bufferSize = 0;
protected $bufferLimit;
protected $flushOnOverflow;
protected $buffer = array();
protected $initialized = false;
/**
* @param HandlerInterface $handler Handler.
* @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param Boolean $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
*/
public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false)
{
parent::__construct($level, $bubble);
$this->handler = $handler;
$this->bufferLimit = (int) $bufferLimit;
$this->flushOnOverflow = $flushOnOverflow;
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($record['level'] < $this->level) {
return false;
}
if (!$this->initialized) {
// __destructor() doesn't get called on Fatal errors
register_shutdown_function(array($this, 'close'));
$this->initialized = true;
}
if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) {
if ($this->flushOnOverflow) {
$this->flush();
} else {
array_shift($this->buffer);
$this->bufferSize--;
}
}
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
$this->buffer[] = $record;
$this->bufferSize++;
return false === $this->bubble;
}
public function flush()
{
if ($this->bufferSize === 0) {
return;
}
$this->handler->handleBatch($this->buffer);
$this->clear();
}
public function __destruct()
{
// suppress the parent behavior since we already have register_shutdown_function()
// to call close(), and the reference contained there will prevent this from being
// GC'd until the end of the request
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->flush();
}
/**
* Clears the buffer without flushing any messages down to the wrapped handler.
*/
public function clear()
{
$this->bufferSize = 0;
$this->buffer = array();
}
}

View File

@ -0,0 +1,211 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\ChromePHPFormatter;
use Monolog\Logger;
/**
* Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)
*
* This also works out of the box with Firefox 43+
*
* @author Christophe Coevoet <stof@notk.org>
*/
class ChromePHPHandler extends AbstractProcessingHandler
{
/**
* Version of the extension
*/
const VERSION = '4.0';
/**
* Header name
*/
const HEADER_NAME = 'X-ChromeLogger-Data';
/**
* Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+)
*/
const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}';
protected static $initialized = false;
/**
* Tracks whether we sent too much data
*
* Chrome limits the headers to 256KB, so when we sent 240KB we stop sending
*
* @var Boolean
*/
protected static $overflowed = false;
protected static $json = array(
'version' => self::VERSION,
'columns' => array('label', 'log', 'backtrace', 'type'),
'rows' => array(),
);
protected static $sendHeaders = true;
/**
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
if (!function_exists('json_encode')) {
throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler');
}
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$messages = array();
foreach ($records as $record) {
if ($record['level'] < $this->level) {
continue;
}
$messages[] = $this->processRecord($record);
}
if (!empty($messages)) {
$messages = $this->getFormatter()->formatBatch($messages);
self::$json['rows'] = array_merge(self::$json['rows'], $messages);
$this->send();
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new ChromePHPFormatter();
}
/**
* Creates & sends header for a record
*
* @see sendHeader()
* @see send()
* @param array $record
*/
protected function write(array $record)
{
self::$json['rows'][] = $record['formatted'];
$this->send();
}
/**
* Sends the log header
*
* @see sendHeader()
*/
protected function send()
{
if (self::$overflowed || !self::$sendHeaders) {
return;
}
if (!self::$initialized) {
self::$initialized = true;
self::$sendHeaders = $this->headersAccepted();
if (!self::$sendHeaders) {
return;
}
self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
}
$json = @json_encode(self::$json);
$data = base64_encode(utf8_encode($json));
if (strlen($data) > 240 * 1024) {
self::$overflowed = true;
$record = array(
'message' => 'Incomplete logs, chrome header size limit reached',
'context' => array(),
'level' => Logger::WARNING,
'level_name' => Logger::getLevelName(Logger::WARNING),
'channel' => 'monolog',
'datetime' => new \DateTime(),
'extra' => array(),
);
self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record);
$json = @json_encode(self::$json);
$data = base64_encode(utf8_encode($json));
}
if (trim($data) !== '') {
$this->sendHeader(self::HEADER_NAME, $data);
}
}
/**
* Send header string to the client
*
* @param string $header
* @param string $content
*/
protected function sendHeader($header, $content)
{
if (!headers_sent() && self::$sendHeaders) {
header(sprintf('%s: %s', $header, $content));
}
}
/**
* Verifies if the headers are accepted by the current user agent
*
* @return Boolean
*/
protected function headersAccepted()
{
if (empty($_SERVER['HTTP_USER_AGENT'])) {
return false;
}
return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']);
}
/**
* BC getter for the sendHeaders property that has been made static
*/
public function __get($property)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
return static::$sendHeaders;
}
/**
* BC setter for the sendHeaders property that has been made static
*/
public function __set($property, $value)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
static::$sendHeaders = $value;
}
}

View File

@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\JsonFormatter;
use Monolog\Logger;
/**
* CouchDB handler
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
class CouchDBHandler extends AbstractProcessingHandler
{
private $options;
public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true)
{
$this->options = array_merge(array(
'host' => 'localhost',
'port' => 5984,
'dbname' => 'logger',
'username' => null,
'password' => null,
), $options);
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$basicAuth = null;
if ($this->options['username']) {
$basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']);
}
$url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname'];
$context = stream_context_create(array(
'http' => array(
'method' => 'POST',
'content' => $record['formatted'],
'ignore_errors' => true,
'max_redirects' => 0,
'header' => 'Content-type: application/json',
),
));
if (false === @file_get_contents($url, null, $context)) {
throw new \RuntimeException(sprintf('Could not connect to %s', $url));
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
}
}

View File

@ -0,0 +1,151 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Logs to Cube.
*
* @link http://square.github.com/cube/
* @author Wan Chen <kami@kamisama.me>
*/
class CubeHandler extends AbstractProcessingHandler
{
private $udpConnection;
private $httpConnection;
private $scheme;
private $host;
private $port;
private $acceptedSchemes = array('http', 'udp');
/**
* Create a Cube handler
*
* @throws \UnexpectedValueException when given url is not a valid url.
* A valid url must consist of three parts : protocol://host:port
* Only valid protocols used by Cube are http and udp
*/
public function __construct($url, $level = Logger::DEBUG, $bubble = true)
{
$urlInfo = parse_url($url);
if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) {
throw new \UnexpectedValueException('URL "'.$url.'" is not valid');
}
if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) {
throw new \UnexpectedValueException(
'Invalid protocol (' . $urlInfo['scheme'] . ').'
. ' Valid options are ' . implode(', ', $this->acceptedSchemes));
}
$this->scheme = $urlInfo['scheme'];
$this->host = $urlInfo['host'];
$this->port = $urlInfo['port'];
parent::__construct($level, $bubble);
}
/**
* Establish a connection to an UDP socket
*
* @throws \LogicException when unable to connect to the socket
* @throws MissingExtensionException when there is no socket extension
*/
protected function connectUdp()
{
if (!extension_loaded('sockets')) {
throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler');
}
$this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0);
if (!$this->udpConnection) {
throw new \LogicException('Unable to create a socket');
}
if (!socket_connect($this->udpConnection, $this->host, $this->port)) {
throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port);
}
}
/**
* Establish a connection to a http server
* @throws \LogicException when no curl extension
*/
protected function connectHttp()
{
if (!extension_loaded('curl')) {
throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler');
}
$this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put');
if (!$this->httpConnection) {
throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port);
}
curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true);
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$date = $record['datetime'];
$data = array('time' => $date->format('Y-m-d\TH:i:s.uO'));
unset($record['datetime']);
if (isset($record['context']['type'])) {
$data['type'] = $record['context']['type'];
unset($record['context']['type']);
} else {
$data['type'] = $record['channel'];
}
$data['data'] = $record['context'];
$data['data']['level'] = $record['level'];
if ($this->scheme === 'http') {
$this->writeHttp(json_encode($data));
} else {
$this->writeUdp(json_encode($data));
}
}
private function writeUdp($data)
{
if (!$this->udpConnection) {
$this->connectUdp();
}
socket_send($this->udpConnection, $data, strlen($data), 0);
}
private function writeHttp($data)
{
if (!$this->httpConnection) {
$this->connectHttp();
}
curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');
curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen('['.$data.']'),
));
Curl\Util::execute($this->httpConnection, 5, false);
}
}

View File

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\Curl;
class Util
{
private static $retriableErrorCodes = array(
CURLE_COULDNT_RESOLVE_HOST,
CURLE_COULDNT_CONNECT,
CURLE_HTTP_NOT_FOUND,
CURLE_READ_ERROR,
CURLE_OPERATION_TIMEOUTED,
CURLE_HTTP_POST_ERROR,
CURLE_SSL_CONNECT_ERROR,
);
/**
* Executes a CURL request with optional retries and exception on failure
*
* @param resource $ch curl handler
* @throws \RuntimeException
*/
public static function execute($ch, $retries = 5, $closeAfterDone = true)
{
while ($retries--) {
if (curl_exec($ch) === false) {
$curlErrno = curl_errno($ch);
if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) {
$curlError = curl_error($ch);
if ($closeAfterDone) {
curl_close($ch);
}
throw new \RuntimeException(sprintf('Curl error (code %s): %s', $curlErrno, $curlError));
}
continue;
}
if ($closeAfterDone) {
curl_close($ch);
}
break;
}
}
}

View File

@ -0,0 +1,169 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Simple handler wrapper that deduplicates log records across multiple requests
*
* It also includes the BufferHandler functionality and will buffer
* all messages until the end of the request or flush() is called.
*
* This works by storing all log records' messages above $deduplicationLevel
* to the file specified by $deduplicationStore. When further logs come in at the end of the
* request (or when flush() is called), all those above $deduplicationLevel are checked
* against the existing stored logs. If they match and the timestamps in the stored log is
* not older than $time seconds, the new log record is discarded. If no log record is new, the
* whole data set is discarded.
*
* This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers
* that send messages to people, to avoid spamming with the same message over and over in case of
* a major component failure like a database server being down which makes all requests fail in the
* same way.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class DeduplicationHandler extends BufferHandler
{
/**
* @var string
*/
protected $deduplicationStore;
/**
* @var int
*/
protected $deduplicationLevel;
/**
* @var int
*/
protected $time;
/**
* @var bool
*/
private $gc = false;
/**
* @param HandlerInterface $handler Handler.
* @param string $deduplicationStore The file/path where the deduplication log should be kept
* @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes
* @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true)
{
parent::__construct($handler, 0, Logger::DEBUG, $bubble, false);
$this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore;
$this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel);
$this->time = $time;
}
public function flush()
{
if ($this->bufferSize === 0) {
return;
}
$passthru = null;
foreach ($this->buffer as $record) {
if ($record['level'] >= $this->deduplicationLevel) {
$passthru = $passthru || !$this->isDuplicate($record);
if ($passthru) {
$this->appendRecord($record);
}
}
}
// default of null is valid as well as if no record matches duplicationLevel we just pass through
if ($passthru === true || $passthru === null) {
$this->handler->handleBatch($this->buffer);
}
$this->clear();
if ($this->gc) {
$this->collectLogs();
}
}
private function isDuplicate(array $record)
{
if (!file_exists($this->deduplicationStore)) {
return false;
}
$store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!is_array($store)) {
return false;
}
$yesterday = time() - 86400;
$timestampValidity = $record['datetime']->getTimestamp() - $this->time;
$expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']);
for ($i = count($store) - 1; $i >= 0; $i--) {
list($timestamp, $level, $message) = explode(':', $store[$i], 3);
if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) {
return true;
}
if ($timestamp < $yesterday) {
$this->gc = true;
}
}
return false;
}
private function collectLogs()
{
if (!file_exists($this->deduplicationStore)) {
return false;
}
$handle = fopen($this->deduplicationStore, 'rw+');
flock($handle, LOCK_EX);
$validLogs = array();
$timestampValidity = time() - $this->time;
while (!feof($handle)) {
$log = fgets($handle);
if (substr($log, 0, 10) >= $timestampValidity) {
$validLogs[] = $log;
}
}
ftruncate($handle, 0);
rewind($handle);
foreach ($validLogs as $log) {
fwrite($handle, $log);
}
flock($handle, LOCK_UN);
fclose($handle);
$this->gc = false;
}
private function appendRecord(array $record)
{
file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND);
}
}

View File

@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;
use Doctrine\CouchDB\CouchDBClient;
/**
* CouchDB handler for Doctrine CouchDB ODM
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
class DoctrineCouchDBHandler extends AbstractProcessingHandler
{
private $client;
public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true)
{
$this->client = $client;
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$this->client->postDocument($record['formatted']);
}
protected function getDefaultFormatter()
{
return new NormalizerFormatter;
}
}

View File

@ -0,0 +1,107 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Aws\Sdk;
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Marshaler;
use Monolog\Formatter\ScalarFormatter;
use Monolog\Logger;
/**
* Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/)
*
* @link https://github.com/aws/aws-sdk-php/
* @author Andrew Lawson <adlawson@gmail.com>
*/
class DynamoDbHandler extends AbstractProcessingHandler
{
const DATE_FORMAT = 'Y-m-d\TH:i:s.uO';
/**
* @var DynamoDbClient
*/
protected $client;
/**
* @var string
*/
protected $table;
/**
* @var int
*/
protected $version;
/**
* @var Marshaler
*/
protected $marshaler;
/**
* @param DynamoDbClient $client
* @param string $table
* @param int $level
* @param bool $bubble
*/
public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true)
{
if (defined('Aws\Sdk::VERSION') && version_compare(Sdk::VERSION, '3.0', '>=')) {
$this->version = 3;
$this->marshaler = new Marshaler;
} else {
$this->version = 2;
}
$this->client = $client;
$this->table = $table;
parent::__construct($level, $bubble);
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$filtered = $this->filterEmptyFields($record['formatted']);
if ($this->version === 3) {
$formatted = $this->marshaler->marshalItem($filtered);
} else {
$formatted = $this->client->formatAttributes($filtered);
}
$this->client->putItem(array(
'TableName' => $this->table,
'Item' => $formatted,
));
}
/**
* @param array $record
* @return array
*/
protected function filterEmptyFields(array $record)
{
return array_filter($record, function ($value) {
return !empty($value) || false === $value || 0 === $value;
});
}
/**
* {@inheritdoc}
*/
protected function getDefaultFormatter()
{
return new ScalarFormatter(self::DATE_FORMAT);
}
}

View File

@ -0,0 +1,128 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\ElasticaFormatter;
use Monolog\Logger;
use Elastica\Client;
use Elastica\Exception\ExceptionInterface;
/**
* Elastic Search handler
*
* Usage example:
*
* $client = new \Elastica\Client();
* $options = array(
* 'index' => 'elastic_index_name',
* 'type' => 'elastic_doc_type',
* );
* $handler = new ElasticSearchHandler($client, $options);
* $log = new Logger('application');
* $log->pushHandler($handler);
*
* @author Jelle Vink <jelle.vink@gmail.com>
*/
class ElasticSearchHandler extends AbstractProcessingHandler
{
/**
* @var Client
*/
protected $client;
/**
* @var array Handler config options
*/
protected $options = array();
/**
* @param Client $client Elastica Client object
* @param array $options Handler configuration
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
$this->client = $client;
$this->options = array_merge(
array(
'index' => 'monolog', // Elastic index name
'type' => 'record', // Elastic document type
'ignore_error' => false, // Suppress Elastica exceptions
),
$options
);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$this->bulkSend(array($record['formatted']));
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
if ($formatter instanceof ElasticaFormatter) {
return parent::setFormatter($formatter);
}
throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter');
}
/**
* Getter options
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new ElasticaFormatter($this->options['index'], $this->options['type']);
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$documents = $this->getFormatter()->formatBatch($records);
$this->bulkSend($documents);
}
/**
* Use Elasticsearch bulk API to send list of documents
* @param array $documents
* @throws \RuntimeException
*/
protected function bulkSend(array $documents)
{
try {
$this->client->addDocuments($documents);
} catch (ExceptionInterface $e) {
if (!$this->options['ignore_error']) {
throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e);
}
}
}
}

View File

@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
/**
* Stores to PHP error_log() handler.
*
* @author Elan Ruusamäe <glen@delfi.ee>
*/
class ErrorLogHandler extends AbstractProcessingHandler
{
const OPERATING_SYSTEM = 0;
const SAPI = 4;
protected $messageType;
protected $expandNewlines;
/**
* @param int $messageType Says where the error should go.
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param Boolean $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
*/
public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false)
{
parent::__construct($level, $bubble);
if (false === in_array($messageType, self::getAvailableTypes())) {
$message = sprintf('The given message type "%s" is not supported', print_r($messageType, true));
throw new \InvalidArgumentException($message);
}
$this->messageType = $messageType;
$this->expandNewlines = $expandNewlines;
}
/**
* @return array With all available types
*/
public static function getAvailableTypes()
{
return array(
self::OPERATING_SYSTEM,
self::SAPI,
);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%');
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
if ($this->expandNewlines) {
$lines = preg_split('{[\r\n]+}', (string) $record['formatted']);
foreach ($lines as $line) {
error_log($line, $this->messageType);
}
} else {
error_log((string) $record['formatted'], $this->messageType);
}
}
}

View File

@ -0,0 +1,140 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Simple handler wrapper that filters records based on a list of levels
*
* It can be configured with an exact list of levels to allow, or a min/max level.
*
* @author Hennadiy Verkh
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class FilterHandler extends AbstractHandler
{
/**
* Handler or factory callable($record, $this)
*
* @var callable|\Monolog\Handler\HandlerInterface
*/
protected $handler;
/**
* Minimum level for logs that are passed to handler
*
* @var int[]
*/
protected $acceptedLevels;
/**
* Whether the messages that are handled can bubble up the stack or not
*
* @var Boolean
*/
protected $bubble;
/**
* @param callable|HandlerInterface $handler Handler or factory callable($record, $this).
* @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided
* @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true)
{
$this->handler = $handler;
$this->bubble = $bubble;
$this->setAcceptedLevels($minLevelOrList, $maxLevel);
if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) {
throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object");
}
}
/**
* @return array
*/
public function getAcceptedLevels()
{
return array_flip($this->acceptedLevels);
}
/**
* @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided
* @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array
*/
public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY)
{
if (is_array($minLevelOrList)) {
$acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList);
} else {
$minLevelOrList = Logger::toMonologLevel($minLevelOrList);
$maxLevel = Logger::toMonologLevel($maxLevel);
$acceptedLevels = array_values(array_filter(Logger::getLevels(), function ($level) use ($minLevelOrList, $maxLevel) {
return $level >= $minLevelOrList && $level <= $maxLevel;
}));
}
$this->acceptedLevels = array_flip($acceptedLevels);
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
return isset($this->acceptedLevels[$record['level']]);
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if (!$this->isHandling($record)) {
return false;
}
// The same logic as in FingersCrossedHandler
if (!$this->handler instanceof HandlerInterface) {
$this->handler = call_user_func($this->handler, $record, $this);
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callable should return a HandlerInterface");
}
}
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
$this->handler->handle($record);
return false === $this->bubble;
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$filtered = array();
foreach ($records as $record) {
if ($this->isHandling($record)) {
$filtered[] = $record;
}
}
$this->handler->handleBatch($filtered);
}
}

View File

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\FingersCrossed;
/**
* Interface for activation strategies for the FingersCrossedHandler.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ActivationStrategyInterface
{
/**
* Returns whether the given record activates the handler.
*
* @param array $record
* @return Boolean
*/
public function isHandlerActivated(array $record);
}

View File

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\FingersCrossed;
use Monolog\Logger;
/**
* Channel and Error level based monolog activation strategy. Allows to trigger activation
* based on level per channel. e.g. trigger activation on level 'ERROR' by default, except
* for records of the 'sql' channel; those should trigger activation on level 'WARN'.
*
* Example:
*
* <code>
* $activationStrategy = new ChannelLevelActivationStrategy(
* Logger::CRITICAL,
* array(
* 'request' => Logger::ALERT,
* 'sensitive' => Logger::ERROR,
* )
* );
* $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy);
* </code>
*
* @author Mike Meessen <netmikey@gmail.com>
*/
class ChannelLevelActivationStrategy implements ActivationStrategyInterface
{
private $defaultActionLevel;
private $channelToActionLevel;
/**
* @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any
* @param array $channelToActionLevel An array that maps channel names to action levels.
*/
public function __construct($defaultActionLevel, $channelToActionLevel = array())
{
$this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel);
$this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel);
}
public function isHandlerActivated(array $record)
{
if (isset($this->channelToActionLevel[$record['channel']])) {
return $record['level'] >= $this->channelToActionLevel[$record['channel']];
}
return $record['level'] >= $this->defaultActionLevel;
}
}

View File

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\FingersCrossed;
use Monolog\Logger;
/**
* Error level based activation strategy.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ErrorLevelActivationStrategy implements ActivationStrategyInterface
{
private $actionLevel;
public function __construct($actionLevel)
{
$this->actionLevel = Logger::toMonologLevel($actionLevel);
}
public function isHandlerActivated(array $record)
{
return $record['level'] >= $this->actionLevel;
}
}

View File

@ -0,0 +1,163 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
use Monolog\Logger;
/**
* Buffers all records until a certain level is reached
*
* The advantage of this approach is that you don't get any clutter in your log files.
* Only requests which actually trigger an error (or whatever your actionLevel is) will be
* in the logs, but they will contain all records, not only those above the level threshold.
*
* You can find the various activation strategies in the
* Monolog\Handler\FingersCrossed\ namespace.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class FingersCrossedHandler extends AbstractHandler
{
protected $handler;
protected $activationStrategy;
protected $buffering = true;
protected $bufferSize;
protected $buffer = array();
protected $stopBuffering;
protected $passthruLevel;
/**
* @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler).
* @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action
* @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true)
* @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered
*/
public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null)
{
if (null === $activationStrategy) {
$activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING);
}
// convert simple int activationStrategy to an object
if (!$activationStrategy instanceof ActivationStrategyInterface) {
$activationStrategy = new ErrorLevelActivationStrategy($activationStrategy);
}
$this->handler = $handler;
$this->activationStrategy = $activationStrategy;
$this->bufferSize = $bufferSize;
$this->bubble = $bubble;
$this->stopBuffering = $stopBuffering;
if ($passthruLevel !== null) {
$this->passthruLevel = Logger::toMonologLevel($passthruLevel);
}
if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) {
throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object");
}
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
return true;
}
/**
* Manually activate this logger regardless of the activation strategy
*/
public function activate()
{
if ($this->stopBuffering) {
$this->buffering = false;
}
if (!$this->handler instanceof HandlerInterface) {
$record = end($this->buffer) ?: null;
$this->handler = call_user_func($this->handler, $record, $this);
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callable should return a HandlerInterface");
}
}
$this->handler->handleBatch($this->buffer);
$this->buffer = array();
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
if ($this->buffering) {
$this->buffer[] = $record;
if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) {
array_shift($this->buffer);
}
if ($this->activationStrategy->isHandlerActivated($record)) {
$this->activate();
}
} else {
$this->handler->handle($record);
}
return false === $this->bubble;
}
/**
* {@inheritdoc}
*/
public function close()
{
if (null !== $this->passthruLevel) {
$level = $this->passthruLevel;
$this->buffer = array_filter($this->buffer, function ($record) use ($level) {
return $record['level'] >= $level;
});
if (count($this->buffer) > 0) {
$this->handler->handleBatch($this->buffer);
$this->buffer = array();
}
}
}
/**
* Resets the state of the handler. Stops forwarding records to the wrapped handler.
*/
public function reset()
{
$this->buffering = true;
}
/**
* Clears the buffer without flushing any messages down to the wrapped handler.
*
* It also resets the handler to its initial buffering state.
*/
public function clear()
{
$this->buffer = array();
$this->reset();
}
}

View File

@ -0,0 +1,195 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\WildfireFormatter;
/**
* Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol.
*
* @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
*/
class FirePHPHandler extends AbstractProcessingHandler
{
/**
* WildFire JSON header message format
*/
const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2';
/**
* FirePHP structure for parsing messages & their presentation
*/
const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1';
/**
* Must reference a "known" plugin, otherwise headers won't display in FirePHP
*/
const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3';
/**
* Header prefix for Wildfire to recognize & parse headers
*/
const HEADER_PREFIX = 'X-Wf';
/**
* Whether or not Wildfire vendor-specific headers have been generated & sent yet
*/
protected static $initialized = false;
/**
* Shared static message index between potentially multiple handlers
* @var int
*/
protected static $messageIndex = 1;
protected static $sendHeaders = true;
/**
* Base header creation function used by init headers & record headers
*
* @param array $meta Wildfire Plugin, Protocol & Structure Indexes
* @param string $message Log message
* @return array Complete header string ready for the client as key and message as value
*/
protected function createHeader(array $meta, $message)
{
$header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta));
return array($header => $message);
}
/**
* Creates message header from record
*
* @see createHeader()
* @param array $record
* @return string
*/
protected function createRecordHeader(array $record)
{
// Wildfire is extensible to support multiple protocols & plugins in a single request,
// but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake.
return $this->createHeader(
array(1, 1, 1, self::$messageIndex++),
$record['formatted']
);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new WildfireFormatter();
}
/**
* Wildfire initialization headers to enable message parsing
*
* @see createHeader()
* @see sendHeader()
* @return array
*/
protected function getInitHeaders()
{
// Initial payload consists of required headers for Wildfire
return array_merge(
$this->createHeader(array('Protocol', 1), self::PROTOCOL_URI),
$this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI),
$this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI)
);
}
/**
* Send header string to the client
*
* @param string $header
* @param string $content
*/
protected function sendHeader($header, $content)
{
if (!headers_sent() && self::$sendHeaders) {
header(sprintf('%s: %s', $header, $content));
}
}
/**
* Creates & sends header for a record, ensuring init headers have been sent prior
*
* @see sendHeader()
* @see sendInitHeaders()
* @param array $record
*/
protected function write(array $record)
{
if (!self::$sendHeaders) {
return;
}
// WildFire-specific headers must be sent prior to any messages
if (!self::$initialized) {
self::$initialized = true;
self::$sendHeaders = $this->headersAccepted();
if (!self::$sendHeaders) {
return;
}
foreach ($this->getInitHeaders() as $header => $content) {
$this->sendHeader($header, $content);
}
}
$header = $this->createRecordHeader($record);
if (trim(current($header)) !== '') {
$this->sendHeader(key($header), current($header));
}
}
/**
* Verifies if the headers are accepted by the current user agent
*
* @return Boolean
*/
protected function headersAccepted()
{
if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) {
return true;
}
return isset($_SERVER['HTTP_X_FIREPHP_VERSION']);
}
/**
* BC getter for the sendHeaders property that has been made static
*/
public function __get($property)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
return static::$sendHeaders;
}
/**
* BC setter for the sendHeaders property that has been made static
*/
public function __set($property, $value)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
static::$sendHeaders = $value;
}
}

View File

@ -0,0 +1,126 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
/**
* Sends logs to Fleep.io using Webhook integrations
*
* You'll need a Fleep.io account to use this handler.
*
* @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation
* @author Ando Roots <ando@sqroot.eu>
*/
class FleepHookHandler extends SocketHandler
{
const FLEEP_HOST = 'fleep.io';
const FLEEP_HOOK_URI = '/hook/';
/**
* @var string Webhook token (specifies the conversation where logs are sent)
*/
protected $token;
/**
* Construct a new Fleep.io Handler.
*
* For instructions on how to create a new web hook in your conversations
* see https://fleep.io/integrations/webhooks/
*
* @param string $token Webhook token
* @param bool|int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @throws MissingExtensionException
*/
public function __construct($token, $level = Logger::DEBUG, $bubble = true)
{
if (!extension_loaded('openssl')) {
throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler');
}
$this->token = $token;
$connectionString = 'ssl://' . self::FLEEP_HOST . ':443';
parent::__construct($connectionString, $level, $bubble);
}
/**
* Returns the default formatter to use with this handler
*
* Overloaded to remove empty context and extra arrays from the end of the log message.
*
* @return LineFormatter
*/
protected function getDefaultFormatter()
{
return new LineFormatter(null, null, true, true);
}
/**
* Handles a log record
*
* @param array $record
*/
public function write(array $record)
{
parent::write($record);
$this->closeSocket();
}
/**
* {@inheritdoc}
*
* @param array $record
* @return string
*/
protected function generateDataStream($record)
{
$content = $this->buildContent($record);
return $this->buildHeader($content) . $content;
}
/**
* Builds the header of the API Call
*
* @param string $content
* @return string
*/
private function buildHeader($content)
{
$header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n";
$header .= "Host: " . self::FLEEP_HOST . "\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($content) . "\r\n";
$header .= "\r\n";
return $header;
}
/**
* Builds the body of API call
*
* @param array $record
* @return string
*/
private function buildContent($record)
{
$dataArray = array(
'message' => $record['formatted'],
);
return http_build_query($dataArray);
}
}

View File

@ -0,0 +1,127 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\FlowdockFormatter;
use Monolog\Formatter\FormatterInterface;
/**
* Sends notifications through the Flowdock push API
*
* This must be configured with a FlowdockFormatter instance via setFormatter()
*
* Notes:
* API token - Flowdock API token
*
* @author Dominik Liebler <liebler.dominik@gmail.com>
* @see https://www.flowdock.com/api/push
*/
class FlowdockHandler extends SocketHandler
{
/**
* @var string
*/
protected $apiToken;
/**
* @param string $apiToken
* @param bool|int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*
* @throws MissingExtensionException if OpenSSL is missing
*/
public function __construct($apiToken, $level = Logger::DEBUG, $bubble = true)
{
if (!extension_loaded('openssl')) {
throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler');
}
parent::__construct('ssl://api.flowdock.com:443', $level, $bubble);
$this->apiToken = $apiToken;
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
if (!$formatter instanceof FlowdockFormatter) {
throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly');
}
return parent::setFormatter($formatter);
}
/**
* Gets the default formatter.
*
* @return FormatterInterface
*/
protected function getDefaultFormatter()
{
throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly');
}
/**
* {@inheritdoc}
*
* @param array $record
*/
protected function write(array $record)
{
parent::write($record);
$this->closeSocket();
}
/**
* {@inheritdoc}
*
* @param array $record
* @return string
*/
protected function generateDataStream($record)
{
$content = $this->buildContent($record);
return $this->buildHeader($content) . $content;
}
/**
* Builds the body of API call
*
* @param array $record
* @return string
*/
private function buildContent($record)
{
return json_encode($record['formatted']['flowdock']);
}
/**
* Builds the header of the API Call
*
* @param string $content
* @return string
*/
private function buildHeader($content)
{
$header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n";
$header .= "Host: api.flowdock.com\r\n";
$header .= "Content-Type: application/json\r\n";
$header .= "Content-Length: " . strlen($content) . "\r\n";
$header .= "\r\n";
return $header;
}
}

View File

@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Gelf\IMessagePublisher;
use Gelf\PublisherInterface;
use Gelf\Publisher;
use InvalidArgumentException;
use Monolog\Logger;
use Monolog\Formatter\GelfMessageFormatter;
/**
* Handler to send messages to a Graylog2 (http://www.graylog2.org) server
*
* @author Matt Lehner <mlehner@gmail.com>
* @author Benjamin Zikarsky <benjamin@zikarsky.de>
*/
class GelfHandler extends AbstractProcessingHandler
{
/**
* @var Publisher the publisher object that sends the message to the server
*/
protected $publisher;
/**
* @param PublisherInterface|IMessagePublisher|Publisher $publisher a publisher object
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($publisher, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
if (!$publisher instanceof Publisher && !$publisher instanceof IMessagePublisher && !$publisher instanceof PublisherInterface) {
throw new InvalidArgumentException('Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance');
}
$this->publisher = $publisher;
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->publisher = null;
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$this->publisher->publish($record['formatted']);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new GelfMessageFormatter();
}
}

View File

@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
/**
* Forwards records to multiple handlers
*
* @author Lenar Lõhmus <lenar@city.ee>
*/
class GroupHandler extends AbstractHandler
{
protected $handlers;
/**
* @param array $handlers Array of Handlers.
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(array $handlers, $bubble = true)
{
foreach ($handlers as $handler) {
if (!$handler instanceof HandlerInterface) {
throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.');
}
}
$this->handlers = $handlers;
$this->bubble = $bubble;
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
foreach ($this->handlers as $handler) {
if ($handler->isHandling($record)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
foreach ($this->handlers as $handler) {
$handler->handle($record);
}
return false === $this->bubble;
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
if ($this->processors) {
$processed = array();
foreach ($records as $record) {
foreach ($this->processors as $processor) {
$processed[] = call_user_func($processor, $record);
}
}
$records = $processed;
}
foreach ($this->handlers as $handler) {
$handler->handleBatch($records);
}
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
foreach ($this->handlers as $handler) {
$handler->setFormatter($formatter);
}
return $this;
}
}

View File

@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
/**
* Interface that all Monolog Handlers must implement
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface HandlerInterface
{
/**
* Checks whether the given record will be handled by this handler.
*
* This is mostly done for performance reasons, to avoid calling processors for nothing.
*
* Handlers should still check the record levels within handle(), returning false in isHandling()
* is no guarantee that handle() will not be called, and isHandling() might not be called
* for a given record.
*
* @param array $record Partial log record containing only a level key
*
* @return Boolean
*/
public function isHandling(array $record);
/**
* Handles a record.
*
* All records may be passed to this method, and the handler should discard
* those that it does not want to handle.
*
* The return value of this function controls the bubbling process of the handler stack.
* Unless the bubbling is interrupted (by returning true), the Logger class will keep on
* calling further handlers in the stack with a given log record.
*
* @param array $record The record to handle
* @return Boolean true means that this handler handled the record, and that bubbling is not permitted.
* false means the record was either not processed or that this handler allows bubbling.
*/
public function handle(array $record);
/**
* Handles a set of records at once.
*
* @param array $records The records to handle (an array of record arrays)
*/
public function handleBatch(array $records);
/**
* Adds a processor in the stack.
*
* @param callable $callback
* @return self
*/
public function pushProcessor($callback);
/**
* Removes the processor on top of the stack and returns it.
*
* @return callable
*/
public function popProcessor();
/**
* Sets the formatter.
*
* @param FormatterInterface $formatter
* @return self
*/
public function setFormatter(FormatterInterface $formatter);
/**
* Gets the formatter.
*
* @return FormatterInterface
*/
public function getFormatter();
}

View File

@ -0,0 +1,108 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
/**
* This simple wrapper class can be used to extend handlers functionality.
*
* Example: A custom filtering that can be applied to any handler.
*
* Inherit from this class and override handle() like this:
*
* public function handle(array $record)
* {
* if ($record meets certain conditions) {
* return false;
* }
* return $this->handler->handle($record);
* }
*
* @author Alexey Karapetov <alexey@karapetov.com>
*/
class HandlerWrapper implements HandlerInterface
{
/**
* @var HandlerInterface
*/
protected $handler;
/**
* HandlerWrapper constructor.
* @param HandlerInterface $handler
*/
public function __construct(HandlerInterface $handler)
{
$this->handler = $handler;
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
return $this->handler->isHandling($record);
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
return $this->handler->handle($record);
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
return $this->handler->handleBatch($records);
}
/**
* {@inheritdoc}
*/
public function pushProcessor($callback)
{
$this->handler->pushProcessor($callback);
return $this;
}
/**
* {@inheritdoc}
*/
public function popProcessor()
{
return $this->handler->popProcessor();
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
$this->handler->setFormatter($formatter);
return $this;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->handler->getFormatter();
}
}

View File

@ -0,0 +1,350 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Sends notifications through the hipchat api to a hipchat room
*
* Notes:
* API token - HipChat API token
* Room - HipChat Room Id or name, where messages are sent
* Name - Name used to send the message (from)
* notify - Should the message trigger a notification in the clients
* version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2)
*
* @author Rafael Dohms <rafael@doh.ms>
* @see https://www.hipchat.com/docs/api
*/
class HipChatHandler extends SocketHandler
{
/**
* Use API version 1
*/
const API_V1 = 'v1';
/**
* Use API version v2
*/
const API_V2 = 'v2';
/**
* The maximum allowed length for the name used in the "from" field.
*/
const MAXIMUM_NAME_LENGTH = 15;
/**
* The maximum allowed length for the message.
*/
const MAXIMUM_MESSAGE_LENGTH = 9500;
/**
* @var string
*/
private $token;
/**
* @var string
*/
private $room;
/**
* @var string
*/
private $name;
/**
* @var bool
*/
private $notify;
/**
* @var string
*/
private $format;
/**
* @var string
*/
private $host;
/**
* @var string
*/
private $version;
/**
* @param string $token HipChat API Token
* @param string $room The room that should be alerted of the message (Id or Name)
* @param string $name Name used in the "from" field.
* @param bool $notify Trigger a notification in clients or not
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $useSSL Whether to connect via SSL.
* @param string $format The format of the messages (default to text, can be set to html if you have html in the messages)
* @param string $host The HipChat server hostname.
* @param string $version The HipChat API version (default HipChatHandler::API_V1)
*/
public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1)
{
if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) {
throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.');
}
$connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80';
parent::__construct($connectionString, $level, $bubble);
$this->token = $token;
$this->name = $name;
$this->notify = $notify;
$this->room = $room;
$this->format = $format;
$this->host = $host;
$this->version = $version;
}
/**
* {@inheritdoc}
*
* @param array $record
* @return string
*/
protected function generateDataStream($record)
{
$content = $this->buildContent($record);
return $this->buildHeader($content) . $content;
}
/**
* Builds the body of API call
*
* @param array $record
* @return string
*/
private function buildContent($record)
{
$dataArray = array(
'notify' => $this->version == self::API_V1 ?
($this->notify ? 1 : 0) :
($this->notify ? 'true' : 'false'),
'message' => $record['formatted'],
'message_format' => $this->format,
'color' => $this->getAlertColor($record['level']),
);
if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) {
if (function_exists('mb_substr')) {
$dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
} else {
$dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
}
}
// if we are using the legacy API then we need to send some additional information
if ($this->version == self::API_V1) {
$dataArray['room_id'] = $this->room;
}
// append the sender name if it is set
// always append it if we use the v1 api (it is required in v1)
if ($this->version == self::API_V1 || $this->name !== null) {
$dataArray['from'] = (string) $this->name;
}
return http_build_query($dataArray);
}
/**
* Builds the header of the API Call
*
* @param string $content
* @return string
*/
private function buildHeader($content)
{
if ($this->version == self::API_V1) {
$header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n";
} else {
// needed for rooms with special (spaces, etc) characters in the name
$room = rawurlencode($this->room);
$header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n";
}
$header .= "Host: {$this->host}\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($content) . "\r\n";
$header .= "\r\n";
return $header;
}
/**
* Assigns a color to each level of log records.
*
* @param int $level
* @return string
*/
protected function getAlertColor($level)
{
switch (true) {
case $level >= Logger::ERROR:
return 'red';
case $level >= Logger::WARNING:
return 'yellow';
case $level >= Logger::INFO:
return 'green';
case $level == Logger::DEBUG:
return 'gray';
default:
return 'yellow';
}
}
/**
* {@inheritdoc}
*
* @param array $record
*/
protected function write(array $record)
{
parent::write($record);
$this->closeSocket();
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
if (count($records) == 0) {
return true;
}
$batchRecords = $this->combineRecords($records);
$handled = false;
foreach ($batchRecords as $batchRecord) {
if ($this->isHandling($batchRecord)) {
$this->write($batchRecord);
$handled = true;
}
}
if (!$handled) {
return false;
}
return false === $this->bubble;
}
/**
* Combines multiple records into one. Error level of the combined record
* will be the highest level from the given records. Datetime will be taken
* from the first record.
*
* @param $records
* @return array
*/
private function combineRecords($records)
{
$batchRecord = null;
$batchRecords = array();
$messages = array();
$formattedMessages = array();
$level = 0;
$levelName = null;
$datetime = null;
foreach ($records as $record) {
$record = $this->processRecord($record);
if ($record['level'] > $level) {
$level = $record['level'];
$levelName = $record['level_name'];
}
if (null === $datetime) {
$datetime = $record['datetime'];
}
$messages[] = $record['message'];
$messageStr = implode(PHP_EOL, $messages);
$formattedMessages[] = $this->getFormatter()->format($record);
$formattedMessageStr = implode('', $formattedMessages);
$batchRecord = array(
'message' => $messageStr,
'formatted' => $formattedMessageStr,
'context' => array(),
'extra' => array(),
);
if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) {
// Pop the last message and implode the remaining messages
$lastMessage = array_pop($messages);
$lastFormattedMessage = array_pop($formattedMessages);
$batchRecord['message'] = implode(PHP_EOL, $messages);
$batchRecord['formatted'] = implode('', $formattedMessages);
$batchRecords[] = $batchRecord;
$messages = array($lastMessage);
$formattedMessages = array($lastFormattedMessage);
$batchRecord = null;
}
}
if (null !== $batchRecord) {
$batchRecords[] = $batchRecord;
}
// Set the max level and datetime for all records
foreach ($batchRecords as &$batchRecord) {
$batchRecord = array_merge(
$batchRecord,
array(
'level' => $level,
'level_name' => $levelName,
'datetime' => $datetime,
)
);
}
return $batchRecords;
}
/**
* Validates the length of a string.
*
* If the `mb_strlen()` function is available, it will use that, as HipChat
* allows UTF-8 characters. Otherwise, it will fall back to `strlen()`.
*
* Note that this might cause false failures in the specific case of using
* a valid name with less than 16 characters, but 16 or more bytes, on a
* system where `mb_strlen()` is unavailable.
*
* @param string $str
* @param int $length
*
* @return bool
*/
private function validateStringLength($str, $length)
{
if (function_exists('mb_strlen')) {
return (mb_strlen($str) <= $length);
}
return (strlen($str) <= $length);
}
}

View File

@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* IFTTTHandler uses cURL to trigger IFTTT Maker actions
*
* Register a secret key and trigger/event name at https://ifttt.com/maker
*
* value1 will be the channel from monolog's Logger constructor,
* value2 will be the level name (ERROR, WARNING, ..)
* value3 will be the log record's message
*
* @author Nehal Patel <nehal@nehalpatel.me>
*/
class IFTTTHandler extends AbstractProcessingHandler
{
private $eventName;
private $secretKey;
/**
* @param string $eventName The name of the IFTTT Maker event that should be triggered
* @param string $secretKey A valid IFTTT secret key
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true)
{
$this->eventName = $eventName;
$this->secretKey = $secretKey;
parent::__construct($level, $bubble);
}
/**
* {@inheritdoc}
*/
public function write(array $record)
{
$postData = array(
"value1" => $record["channel"],
"value2" => $record["level_name"],
"value3" => $record["message"],
);
$postString = json_encode($postData);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postString);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Content-Type: application/json",
));
Curl\Util::execute($ch);
}
}

View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* @author Robert Kaufmann III <rok3@rok3.me>
*/
class LogEntriesHandler extends SocketHandler
{
/**
* @var string
*/
protected $logToken;
/**
* @param string $token Log token supplied by LogEntries
* @param bool $useSSL Whether or not SSL encryption should be used.
* @param int $level The minimum logging level to trigger this handler
* @param bool $bubble Whether or not messages that are handled should bubble up the stack.
*
* @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
*/
public function __construct($token, $useSSL = true, $level = Logger::DEBUG, $bubble = true)
{
if ($useSSL && !extension_loaded('openssl')) {
throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler');
}
$endpoint = $useSSL ? 'ssl://data.logentries.com:443' : 'data.logentries.com:80';
parent::__construct($endpoint, $level, $bubble);
$this->logToken = $token;
}
/**
* {@inheritdoc}
*
* @param array $record
* @return string
*/
protected function generateDataStream($record)
{
return $this->logToken . ' ' . $record['formatted'];
}
}

View File

@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\LogglyFormatter;
/**
* Sends errors to Loggly.
*
* @author Przemek Sobstel <przemek@sobstel.org>
* @author Adam Pancutt <adam@pancutt.com>
* @author Gregory Barchard <gregory@barchard.net>
*/
class LogglyHandler extends AbstractProcessingHandler
{
const HOST = 'logs-01.loggly.com';
const ENDPOINT_SINGLE = 'inputs';
const ENDPOINT_BATCH = 'bulk';
protected $token;
protected $tag = array();
public function __construct($token, $level = Logger::DEBUG, $bubble = true)
{
if (!extension_loaded('curl')) {
throw new \LogicException('The curl extension is needed to use the LogglyHandler');
}
$this->token = $token;
parent::__construct($level, $bubble);
}
public function setTag($tag)
{
$tag = !empty($tag) ? $tag : array();
$this->tag = is_array($tag) ? $tag : array($tag);
}
public function addTag($tag)
{
if (!empty($tag)) {
$tag = is_array($tag) ? $tag : array($tag);
$this->tag = array_unique(array_merge($this->tag, $tag));
}
}
protected function write(array $record)
{
$this->send($record["formatted"], self::ENDPOINT_SINGLE);
}
public function handleBatch(array $records)
{
$level = $this->level;
$records = array_filter($records, function ($record) use ($level) {
return ($record['level'] >= $level);
});
if ($records) {
$this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH);
}
}
protected function send($data, $endpoint)
{
$url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token);
$headers = array('Content-Type: application/json');
if (!empty($this->tag)) {
$headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
Curl\Util::execute($ch);
}
protected function getDefaultFormatter()
{
return new LogglyFormatter();
}
}

View File

@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
/**
* Base class for all mail handlers
*
* @author Gyula Sallai
*/
abstract class MailHandler extends AbstractProcessingHandler
{
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$messages = array();
foreach ($records as $record) {
if ($record['level'] < $this->level) {
continue;
}
$messages[] = $this->processRecord($record);
}
if (!empty($messages)) {
$this->send((string) $this->getFormatter()->formatBatch($messages), $messages);
}
}
/**
* Send a mail with the given content
*
* @param string $content formatted email body to be sent
* @param array $records the array of log records that formed this content
*/
abstract protected function send($content, array $records);
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$this->send((string) $record['formatted'], array($record));
}
protected function getHighestRecord(array $records)
{
$highestRecord = null;
foreach ($records as $record) {
if ($highestRecord === null || $highestRecord['level'] < $record['level']) {
$highestRecord = $record;
}
}
return $highestRecord;
}
}

Some files were not shown because too many files have changed in this diff Show More