<?php

if ( ! defined( 'WP_DISABLE_FATAL_ERROR_HANDLER' ) ) {
	define( 'WP_DISABLE_FATAL_ERROR_HANDLER', true );
}

if ( ! defined( 'WP_DEBUG' ) ) {
	define( 'WP_DEBUG', false );
}

if ( ! defined( 'WP_DEBUG_LOG' ) ) {
	define( 'WP_DEBUG_LOG', false );
}

if ( ! defined( 'WP_DEBUG_DISPLAY' ) ) {
	define( 'WP_DEBUG_DISPLAY', false );
}

function wpd_platform_base_dir() {
	$path = dirname( __FILE__ );
	while ( true ) {
		if ( file_exists( $path . '/wp-config.php' ) ) {
			return $path . '/';
		}
		$path = dirname( $path );
	}
}

// Require the wp-load.php file (which loads wp-config.php and bootstraps WordPress).
require wpd_platform_base_dir() . '/wp-load.php';

define( 'SHORTINIT', true );

// Restrict access to page.
if ( $_COOKIE['wpd_allow_access'] == false ) {
	\WPD_Platform\Plugin::instance()->get_host()->restrict_access();
}

/**
 * OPcache GUI
 *
 * A simple but effective single-file GUI for the OPcache PHP extension.
 *
 * @author Andrew Collington, andy@amnuts.com
 * @version 2.2.3
 * @link https://github.com/amnuts/opcache-gui
 * @license MIT, http://acollington.mit-license.org/
 */


/*
 * User configuration
 */

$options = [
	'allow_filelist'   => true,  // show/hide the files tab
	'allow_invalidate' => true,  // give a link to invalidate files
	'allow_reset'      => true,  // give option to reset the whole cache
	'allow_realtime'   => true,  // give option to enable/disable real-time updates
	'refresh_time'     => 5,     // how often the data will refresh, in seconds
	'size_precision'   => 2,     // Digits after decimal point
	'size_space'       => false, // have '1MB' or '1 MB' when showing sizes
	'charts'           => true,  // show gauge chart or just big numbers
	'debounce_rate'    => 250,    // milliseconds after key press to send keyup event when filtering
];

/*
 * Shouldn't need to alter anything else below here
 */

if ( ! extension_loaded( 'Zend OPcache' ) ) {
	die( 'The Zend OPcache extension does not appear to be installed' );
}

class OpCacheService {

	protected $data;
	protected $options;
	protected $defaults = [
		'allow_filelist'   => true,
		'allow_invalidate' => true,
		'allow_reset'      => true,
		'allow_realtime'   => true,
		'refresh_time'     => 5,
		'size_precision'   => 2,
		'size_space'       => false,
		'charts'           => true,
		'debounce_rate'    => 250,
	];

	private function __construct( $options = [] ) {
		$this->options = array_merge( $this->defaults, $options );
		$this->data    = $this->compileState();
	}

	public static function init( $options = [] ) {
		$self = new self( $options );
		if ( ! empty( $_SERVER['HTTP_X_REQUESTED_WITH'] )
			&& strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'xmlhttprequest'
		) {
			if ( isset( $_GET['reset'] ) && $self->getOption( 'allow_reset' ) ) {
				echo '{ "success": "' . ( $self->resetCache() ? 'yes' : 'no' ) . '" }';
			} elseif ( isset( $_GET['invalidate'] ) && $self->getOption( 'allow_invalidate' ) ) {
				echo '{ "success": "' . ( $self->resetCache( $_GET['invalidate'] ) ? 'yes' : 'no' ) . '" }';
			} else {
				echo json_encode( $self->getData( ( empty( $_GET['section'] ) ? null : $_GET['section'] ) ) );
			}
			exit;
		} elseif ( isset( $_GET['reset'] ) && $self->getOption( 'allow_reset' ) ) {
			$self->resetCache();
			header( 'Location: ?' );
			exit;
		} elseif ( isset( $_GET['invalidate'] ) && $self->getOption( 'allow_invalidate' ) ) {
			$self->resetCache( $_GET['invalidate'] );
			header( 'Location: ?' );
			exit;
		}
		return $self;
	}

	public function getOption( $name = null ) {
		if ( $name === null ) {
			return $this->options;
		}
		return ( isset( $this->options[ $name ] )
			? $this->options[ $name ]
			: null
		);
	}

	public function getData( $section = null, $property = null ) {
		if ( $section === null ) {
			return $this->data;
		}
		$section = strtolower( $section );
		if ( isset( $this->data[ $section ] ) ) {
			if ( $property === null || ! isset( $this->data[ $section ][ $property ] ) ) {
				return $this->data[ $section ];
			}
			return $this->data[ $section ][ $property ];
		}
		return null;
	}

	public function canInvalidate() {
		return ( $this->getOption( 'allow_invalidate' ) && function_exists( 'opcache_invalidate' ) );
	}

	public function resetCache( $file = null ) {
		$success = false;
		if ( $file === null ) {
			$success = opcache_reset();
		} elseif ( function_exists( 'opcache_invalidate' ) ) {
			$success = opcache_invalidate( urldecode( $file ), true );
		}
		if ( $success ) {
			$this->compileState();
		}
		return $success;
	}

	protected function size( $size ) {
		$i   = 0;
		$val = array( 'b', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' );
		while ( ( $size / 1024 ) > 1 ) {
			$size /= 1024;
			++$i;
		}
		return sprintf(
			'%.' . $this->getOption( 'size_precision' ) . 'f%s%s',
			$size,
			( $this->getOption( 'size_space' ) ? ' ' : '' ),
			$val[ $i ]
		);
	}

	protected function compileState() {
		$status = opcache_get_status();
		$config = opcache_get_configuration();

		$files = [];
		if ( ! empty( $status['scripts'] ) && $this->getOption( 'allow_filelist' ) ) {
			uasort(
				$status['scripts'],
				function( $a, $b ) {
					return $a['hits'] < $b['hits'];
				}
			);
			foreach ( $status['scripts'] as &$file ) {
				$file['full_path'] = str_replace( '\\', '/', $file['full_path'] );
				$file['readable']  = [
					'hits'               => number_format( $file['hits'] ),
					'memory_consumption' => $this->size( $file['memory_consumption'] ),
				];
			}
			$files = array_values( $status['scripts'] );
		}

		$overview = array_merge(
			$status['memory_usage'],
			$status['opcache_statistics'],
			[
				'used_memory_percentage' => round(
					100 * (
						( $status['memory_usage']['used_memory'] + $status['memory_usage']['wasted_memory'] )
						/ $config['directives']['opcache.memory_consumption'] )
				),
				'hit_rate_percentage'    => round( $status['opcache_statistics']['opcache_hit_rate'] ),
				'wasted_percentage'      => round( $status['memory_usage']['current_wasted_percentage'], 2 ),
				'readable'               => [
					'total_memory'       => $this->size( $config['directives']['opcache.memory_consumption'] ),
					'used_memory'        => $this->size( $status['memory_usage']['used_memory'] ),
					'free_memory'        => $this->size( $status['memory_usage']['free_memory'] ),
					'wasted_memory'      => $this->size( $status['memory_usage']['wasted_memory'] ),
					'num_cached_scripts' => number_format( $status['opcache_statistics']['num_cached_scripts'] ),
					'hits'               => number_format( $status['opcache_statistics']['hits'] ),
					'misses'             => number_format( $status['opcache_statistics']['misses'] ),
					'blacklist_miss'     => number_format( $status['opcache_statistics']['blacklist_misses'] ),
					'num_cached_keys'    => number_format( $status['opcache_statistics']['num_cached_keys'] ),
					'max_cached_keys'    => number_format( $status['opcache_statistics']['max_cached_keys'] ),
					'start_time'         => date( 'Y-m-d H:i:s', $status['opcache_statistics']['start_time'] ),
					'last_restart_time'  => ( $status['opcache_statistics']['last_restart_time'] == 0
							? 'never'
							: date( 'Y-m-d H:i:s', $status['opcache_statistics']['last_restart_time'] )
						),
				],
			]
		);

		$directives = [];
		ksort( $config['directives'] );
		foreach ( $config['directives'] as $k => $v ) {
			$directives[] = [
				'k' => $k,
				'v' => $v,
			];
		}

		$version = array_merge(
			$config['version'],
			[
				'php'    => phpversion(),
				'server' => $_SERVER['SERVER_SOFTWARE'],
				'host'   => ( function_exists( 'gethostname' )
					? gethostname()
					: ( php_uname( 'n' )
						?: ( empty( $_SERVER['SERVER_NAME'] )
							? $_SERVER['HOST_NAME']
							: $_SERVER['SERVER_NAME']
						)
					)
				),
			]
		);

		return [
			'version'    => $version,
			'overview'   => $overview,
			'files'      => $files,
			'directives' => $directives,
			'blacklist'  => $config['blacklist'],
			'functions'  => get_extension_funcs( 'Zend OPcache' ),
		];
	}
}

$opcache = OpCacheService::init( $options );

?>
<!doctype html>
<html>
<head>
	<meta charset="UTF-8"/>
	<meta name="viewport" content="width=device-width,initial-scale=1.0">
	<title>OPcache statistics on <?php echo $opcache->getData( 'version', 'host' ); ?></title>
	<script src="//cdn.jsdelivr.net/react/15.0.1/react.min.js"></script>
	<script src="//cdn.jsdelivr.net/react/15.0.1/react-dom.min.js"></script>
	<script src="//code.jquery.com/jquery-2.2.3.min.js"></script>
	<style type="text/css">
		body { font-family:sans-serif; font-size:90%; padding: 0; margin: 0 }
		nav { padding-top: 20px; }
		nav > ul { list-style-type: none; padding-left: 8px; margin: 0; border-bottom: 1px solid #ccc; }
		nav > ul > li { display: inline-block; padding: 0; margin: 0 0 -1px 0; }
		nav > ul > li > a { display: block; margin: 0 10px; padding: 15px 30px; border: 1px solid transparent; border-bottom-color: #ccc; text-decoration: none; }
		nav > ul > li > a:hover { background-color: #f4f4f4; text-decoration: underline; }
		nav > ul > li > a.active:hover { background-color: initial; }
		nav > ul > li > a[data-for].active { border: 1px solid #ccc; border-bottom-color: #ffffff; border-top: 3px solid #6ca6ef; }
		table { margin: 0 0 1em 0; border-collapse: collapse; border-color: #fff; width: 100%; table-layout: fixed; }
		table caption { text-align: left; font-size: 1.5em; }
		table tr { background-color: #99D0DF; border-color: #fff; }
		table th { text-align: left; padding: 6px; background-color: #6ca6ef; color: #fff; border-color: #fff; font-weight: normal; }
		table td { padding: 4px 6px; line-height: 1.4em; vertical-align: top; border-color: #fff; }
		table tr:nth-child(odd) { background-color: #EFFEFF; }
		table tr:nth-child(even) { background-color: #E0ECEF; }
		#filelist table tr { background-color: #EFFEFF; }
		#filelist table tr.alternate { background-color: #E0ECEF; }
		td.pathname { width: 70%; }
		footer { border-top: 1px solid #ccc; padding: 1em 2em; }
		footer a { padding: 2em; text-decoration: none; opacity: 0.7; }
		footer a:hover { opacity: 1; }
		canvas { display: block; width: 250px; height: 250px; margin: 0 auto; }
		#tabs { padding: 2em; }
		#tabs > div { display: none; }
		#tabs > div#overview { display:block; }
		#resetCache, #toggleRealtime, footer > a { background-position: 5px 50%; background-repeat: no-repeat; background-color: transparent; }
		footer > a { background-position: 0 50%; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAQCAYAAAAbBi9cAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjE2MENCRkExNzVBQjExRTQ5NDBGRTUzMzQyMDVDNzFFIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjE2MENCRkEyNzVBQjExRTQ5NDBGRTUzMzQyMDVDNzFFIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MTYwQ0JGOUY3NUFCMTFFNDk0MEZFNTMzNDIwNUM3MUUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MTYwQ0JGQTA3NUFCMTFFNDk0MEZFNTMzNDIwNUM3MUUiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7HtUU1AAABN0lEQVR42qyUvWoCQRSF77hCLLKC+FOlCKTyIbYQUuhbWPkSFnZ2NpabUvANLGyz5CkkYGMlFtFAUmiSM8lZOVkWsgm58K079+fMnTusZl92BXbgDrTtZ2szd8fas/XBOzmBKaiCEFyTkL4pc9L8vgpNJJDyWtDna61EoXpO+xcFfXUVqtrf7Vx7m9Pub/EatvgHoYXD4ylztC14BBVwydvydgDPHPgNaErN3jLKIxAUmEvAXK21I18SJpXBGAxyBAaMlblOWOs1bMXFkMGeBFsi0pJNe/QNuV7563+gs8LfhrRfE6GaHLuRqfnUiKi6lJ034B44EXL0baTTJWujNGkG3kBX5uRyZuRkPl3WzDTBtzjnxxiDDq83yNxUk7GYuXM53jeLuMNavvAXkv4zrJkTaeGHAAMAIal3icPMsyQAAAAASUVORK5CYII='); font-size: 80%; }
		#resetCache { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NjBFMUMyMjI3NDlGMTFFNEE3QzNGNjQ0OEFDQzQ1MkMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NjBFMUMyMjM3NDlGMTFFNEE3QzNGNjQ0OEFDQzQ1MkMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MEUxQzIyMDc0OUYxMUU0QTdDM0Y2NDQ4QUNDNDUyQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo2MEUxQzIyMTc0OUYxMUU0QTdDM0Y2NDQ4QUNDNDUyQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PplZ+ZkAAAD1SURBVHjazFPtDYJADIUJZAMZ4UbACWQENjBO4Ao6AW5AnODcADZQJwAnwJ55NbWhB/6zycsdpX39uDZNpsURtjgzwkDoCBecs5ITPGGMwCNAkIrQw+8ri36GhBHsavFdpILEo4wEpZxRigy009EhG760gr0VhFoyZfvJKPwsheIWIeGejBZRIxRVhMRFevbuUXBew/iE/lhlBduV0j8Jx+TvJEWPphq8n5li9utgaw6cW/h6NSt/JcnVBhQxotIgKTBrbNvIHo2G0x1rwlKqTDusxiAz6hHNL1zayTVqVKRKpa/LPljPH1sJh6l/oNSrZfwSYABtq3tFdZA5BAAAAABJRU5ErkJggg=='); }
		#toggleRealtime { position: relative; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAYAAACAl21KAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODE5RUU4NUE3NDlGMTFFNDkyMzA4QzY1RjRBQkIzQjUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODE5RUU4NUI3NDlGMTFFNDkyMzA4QzY1RjRBQkIzQjUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4MTlFRTg1ODc0OUYxMUU0OTIzMDhDNjVGNEFCQjNCNSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4MTlFRTg1OTc0OUYxMUU0OTIzMDhDNjVGNEFCQjNCNSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpXjpvMAAAD2SURBVHjarFQBEcMgDKR3E1AJldA5wMEqAQmTgINqmILdFCChdUAdMAeMcukuSwnQbbnLlZLwJPkQIcrSiT/IGNQHNb8CGQDyRw+2QWUBqC+luzo4OKQZIAVrB+ssyKp3Bkijf0+ijzIh4wQppoBauMSjyDZfMSCDxYZMsfHF120T36AqWZMkgyguQ3GOfottJ5TKnHC+wfeRsC2oDVayPgr3bbN2tHBH3tWuJCPa0JUgKtFzMQrcZH3FNHAc0yOp1cCASALyngoN6lhDopkJWxdifwY9A3u7l29ImpxOFSWIOVsGwHKENIWxss2eBVKdOeeXAAMAk/Z9h4QhXmUAAAAASUVORK5CYII='); }
		#counts { width: 270px; float: right; }
		#counts > div > div { background-color: #ededed; margin-bottom: 10px; }
		#counts > div > div > h3 { background-color: #cdcdcd; padding: 4px 6px; margin: 0; text-align: center; }
		#counts > div > div > p { margin: 0; text-align: center; }
		#counts > div > div > p span.large + span { font-size: 20pt; margin: 0; color: #6ca6ef; }
		#counts > div > div > p span.large { color: #6ca6ef; font-size: 80pt; margin: 0; padding: 0; text-align: center; }
		#info { margin-right: 280px; }
		#frmFilter { width: 520px; }
		#moreinfo { padding: 10px; }
		#moreinfo > p { text-align: left !important; line-height: 180%; }
		.metainfo { font-size: 80%; }
		.hide { display: none; }
		#toggleRealtime.pulse::before {
			content: ""; position: absolute;
			top: 13px; left: 3px; width: 18px; height: 18px;
			z-index: 10; opacity: 0; background-color: transparent;
			border: 2px solid rgb(255, 116, 0); border-radius: 100%;
			-webkit-animation: pulse 1s linear 2;
			-moz-animation: pulse 1s linear 2;
			animation: pulse 1s linear 2;
		}
		@media screen and (max-width: 750px) {
			#info { margin-right:auto; clear:both; }
			nav > ul { border-bottom: 0; }
			nav > ul > li { display: block; margin: 0; }
			nav > ul > li > a { display: block; margin: 0 10px; padding: 10px 0 10px 30px; border: 0; }
			nav > ul > li > a[data-for].active { border-bottom-color: #ccc; }
			#counts { position:relative; display:block; width:100%; }
			#toggleRealtime.pulse::before { top: 8px; }
		}
		@media screen and (max-width: 550px) {
			#frmFilter { width: 100%; }
		}
		@keyframes pulse {
			0% {transform: scale(1); opacity: 0;}
			50% {transform: scale(1.3); opacity: 0.7;}
			100% {transform: scale(1.6); opacity: 1;}
		}
		@-webkit-keyframes pulse {
			0% {-webkit-transform: scale(1); opacity: 0;}
			50% {-webkit-transform: scale(1.3); opacity: 0.7;}
			100% {-webkit-transform: scale(1.6); opacity: 0;}
		}
		@-moz-keyframes pulse {
			0% {-moz-transform: scale(1); opacity: 0;}
			50% {-moz-transform: scale(1.3); opacity: 0.7;}
			100% {-moz-transform: scale(1.6); opacity: 0;}
		}
	</style>
</head>

<body>

<header>
	<nav>
		<ul>
			<li><a data-for="overview" href="#overview" class="active">Overview</a></li>
			<?php if ( $opcache->getOption( 'allow_filelist' ) ) : ?>
			<li><a data-for="files" href="#files">File usage</a></li>
			<?php endif; ?>
			<?php if ( $opcache->getOption( 'allow_reset' ) ) : ?>
			<li><a href="?reset=1" id="resetCache" onclick="return confirm('Are you sure you want to reset the cache?');">Reset cache</a></li>
			<?php endif; ?>
			<?php if ( $opcache->getOption( 'allow_realtime' ) ) : ?>
			<li><a href="#" id="toggleRealtime">Enable real-time update</a></li>
			<?php endif; ?>
		</ul>
	</nav>
</header>

<div id="tabs">
	<div id="overview">
		<div class="container">
			<div id="counts"></div>
			<div id="info">
				<div id="generalInfo"></div>
				<div id="directives"></div>
				<div id="functions">
					<table>
						<thead>
							<tr><th>Available functions</th></tr>
						</thead>
						<tbody>
							<?php foreach ( $opcache->getData( 'functions' ) as $func ) : ?>
							<tr><td><a href="http://php.net/<?php echo $func; ?>" title="View manual page" target="_blank"><?php echo $func; ?></a></td></tr>
							<?php endforeach; ?>
						</tbody>
					</table>
				</div>
				<br style="clear:both;" />
			</div>
		</div>
	</div>
	<div id="files">
		<?php if ( $opcache->getOption( 'allow_filelist' ) ) : ?>
		<p><label>Start typing to filter on script path<br/><input type="text" name="filter" id="frmFilter" /><label></p>
		<?php endif; ?>
		<div class="container" id="filelist"></div>
	</div>
</div>

<footer>
	<a href="https://github.com/amnuts/opcache-gui" target="_blank">https://github.com/amnuts/opcache-gui</a>
</footer>

<script type="text/javascript">
	var realtime = false;
	var opstate = <?php echo json_encode( $opcache->getData() ); ?>;
	var canInvalidate = <?php echo json_encode( $opcache->canInvalidate() ); ?>;
	var useCharts = <?php echo json_encode( $opcache->getOption( 'charts' ) ); ?>;
	var allowFiles = <?php echo json_encode( $opcache->getOption( 'allow_filelist' ) ); ?>;
	var debounce = function(func, wait, immediate) {
		var timeout;
		wait = wait || 250;
		return function() {
			var context = this, args = arguments;
			var later = function() {
				timeout = null;
				if (!immediate) {
					func.apply(context, args);
				}
			};
			var callNow = immediate && !timeout;
			clearTimeout(timeout);
			timeout = setTimeout(later, wait);
			if (callNow) {
				func.apply(context, args);
			}
		};
	};
	function keyUp(event){
		var compare = $('#frmFilter').val().toLowerCase();
		$('#filelist').find('table tbody tr').each(function(index){
			if ($(this).data('path').indexOf(compare) == -1) {
				$(this).addClass('hide');
			} else {
				$(this).removeClass('hide');
			}
		});
		$('#filelist table tbody').trigger('paint');
	};

	<?php if ( $opcache->getOption( 'charts' ) ) : ?>
	var Gauge = function(el, colour) {
		this.canvas  = $(el).get(0);
		this.ctx     = this.canvas.getContext('2d');
		this.width   = this.canvas.width;
		this.height  = this.canvas.height;
		this.colour  = colour || '#6ca6ef';
		this.loop    = null;
		this.degrees = 0;
		this.newdegs = 0;
		this.text    = '';
		this.init = function() {
			this.ctx.clearRect(0, 0, this.width, this.height);
			this.ctx.beginPath();
			this.ctx.strokeStyle = '#e2e2e2';
			this.ctx.lineWidth = 30;
			this.ctx.arc(this.width/2, this.height/2, 100, 0, Math.PI*2, false);
			this.ctx.stroke();
			this.ctx.beginPath();
			this.ctx.strokeStyle = this.colour;
			this.ctx.lineWidth = 30;
			this.ctx.arc(this.width/2, this.height/2, 100, 0 - (90 * Math.PI / 180), (this.degrees * Math.PI / 180) - (90 * Math.PI / 180), false);
			this.ctx.stroke();
			this.ctx.fillStyle = this.colour;
			this.ctx.font = '60px sans-serif';
			this.text = Math.round((this.degrees/360)*100) + '%';
			this.ctx.fillText(this.text, (this.width/2) - (this.ctx.measureText(this.text).width/2), (this.height/2) + 20);
		};
		this.draw = function() {
			if (typeof this.loop != 'undefined') {
				clearInterval(this.loop);
			}
			var self = this;
			self.loop = setInterval(function(){ self.animate(); }, 1000/(this.newdegs - this.degrees));
		};
		this.animate = function() {
			if (this.degrees == this.newdegs) {
				clearInterval(this.loop);
			}
			if (this.degrees < this.newdegs) {
				++this.degrees;
			} else {
				--this.degrees;
			}
			this.init();
		};
		this.setValue = function(val) {
			this.newdegs = Math.round(3.6 * val);
			this.draw();
		};
	}
	<?php endif; ?>

	$(function(){
		<?php if ( $opcache->getOption( 'allow_realtime' ) ) : ?>
		function updateStatus() {
			$('#toggleRealtime').removeClass('pulse');
			$.ajax({
				url: "#",
				dataType: "json",
				cache: false,
				success: function(data) {
					$('#toggleRealtime').addClass('pulse');
					opstate = data;
					overviewCountsObj.setState({
						data : opstate.overview
					});
					generalInfoObj.setState({
						version : opstate.version,
						start : opstate.overview.readable.start_time,
						reset : opstate.overview.readable.last_restart_time
					});
					filesObj.setState({
						data : opstate.files,
						count_formatted : opstate.overview.readable.num_cached_scripts,
						count : opstate.overview.num_cached_scripts
					});
					keyUp();
				}
			});
		}
		$('#toggleRealtime').click(function(){
			if (realtime === false) {
				realtime = setInterval(function(){updateStatus()}, <?php echo (int) $opcache->getOption( 'refresh_time' ) * 1000; ?>);
				$(this).text('Disable real-time update');
			} else {
				clearInterval(realtime);
				realtime = false;
				$(this).text('Enable real-time update').removeClass('pulse');
			}
		});
		<?php endif; ?>
		$('nav a[data-for]').click(function(){
			$('#tabs > div').hide();
			$('#' + $(this).data('for')).show();
			$('nav a[data-for]').removeClass('active');
			$(this).addClass('active');
			return false;
		});
		$(document).on('paint', '#filelist table tbody', function(event, params) {
			var trs = $('#filelist').find('tbody tr');
			trs.removeClass('alternate');
			trs.filter(':not(.hide):odd').addClass('alternate');
			filesObj.setState({showing: trs.filter(':not(.hide)').length});
		});
		$('#frmFilter').bind('keyup', debounce(keyUp, <?php echo $opcache->getOption( 'debounce_rate' ); ?>));
	});

	var MemoryUsage = React.createClass({displayName: "MemoryUsage",
		getInitialState: function() {
			return {
				memoryUsageGauge : null
			};
		},
		componentDidMount: function() {
			if (this.props.chart) {
				this.state.memoryUsageGauge = new Gauge('#memoryUsageCanvas');
				this.state.memoryUsageGauge.setValue(this.props.value);
			}
		},
		componentDidUpdate: function() {
			if (this.state.memoryUsageGauge != null) {
				this.state.memoryUsageGauge.setValue(this.props.value);
			}
		},
		render: function() {
			if (this.props.chart == true) {
				return(React.createElement("canvas", {id: "memoryUsageCanvas", width: "250", height: "250", "data-value": this.props.value}));
			}
			return(React.createElement("p", null, React.createElement("span", {className: "large"}, this.props.value), React.createElement("span", null, "%")));
		}
	});

	var HitRate = React.createClass({displayName: "HitRate",
		getInitialState: function() {
			return {
				hitRateGauge : null
			};
		},
		componentDidMount: function() {
			if (this.props.chart) {
				this.state.hitRateGauge = new Gauge('#hitRateCanvas');
				this.state.hitRateGauge.setValue(this.props.value)
			}
		},
		componentDidUpdate: function() {
			if (this.state.hitRateGauge != null) {
				this.state.hitRateGauge.setValue(this.props.value);
			}
		},
		render: function() {
			if (this.props.chart == true) {
				return(React.createElement("canvas", {id: "hitRateCanvas", width: "250", height: "250", "data-value": this.props.value}));
			}
			return(React.createElement("p", null, React.createElement("span", {className: "large"}, this.props.value), React.createElement("span", null, "%")));
		}
	});

	var OverviewCounts = React.createClass({displayName: "OverviewCounts",
		getInitialState: function() {
			return {
				data  : opstate.overview,
				chart : useCharts
			};
		},
		render: function() {
			return (
				React.createElement("div", null,
					React.createElement("div", null,
						React.createElement("h3", null, "memory usage"),
						React.createElement("p", null, React.createElement(MemoryUsage, {chart: this.state.chart, value: this.state.data.used_memory_percentage}))
					),
					React.createElement("div", null,
						React.createElement("h3", null, "hit rate"),
						React.createElement("p", null, React.createElement(HitRate, {chart: this.state.chart, value: this.state.data.hit_rate_percentage}))
					),
					React.createElement("div", {id: "moreinfo"},
						React.createElement("p", null, React.createElement("b", null, "total memory:"), " ", this.state.data.readable.total_memory),
						React.createElement("p", null, React.createElement("b", null, "used memory:"), " ", this.state.data.readable.used_memory),
						React.createElement("p", null, React.createElement("b", null, "free memory:"), " ", this.state.data.readable.free_memory),
						React.createElement("p", null, React.createElement("b", null, "wasted memory:"), " ", this.state.data.readable.wasted_memory, " (", this.state.data.wasted_percentage, "%)"),
						React.createElement("p", null, React.createElement("b", null, "number of cached files:"), " ", this.state.data.readable.num_cached_scripts),
						React.createElement("p", null, React.createElement("b", null, "number of hits:"), " ", this.state.data.readable.hits),
						React.createElement("p", null, React.createElement("b", null, "number of misses:"), " ", this.state.data.readable.misses),
						React.createElement("p", null, React.createElement("b", null, "blacklist misses:"), " ", this.state.data.readable.blacklist_miss),
						React.createElement("p", null, React.createElement("b", null, "number of cached keys:"), " ", this.state.data.readable.num_cached_keys),
						React.createElement("p", null, React.createElement("b", null, "max cached keys:"), " ", this.state.data.readable.max_cached_keys)
					)
				)
			);
		}
	});

	var GeneralInfo = React.createClass({displayName: "GeneralInfo",
		getInitialState: function() {
			return {
				version : opstate.version,
				start : opstate.overview.readable.start_time,
				reset : opstate.overview.readable.last_restart_time
			};
		},
		render: function() {
			return (
				React.createElement("table", null,
					React.createElement("thead", null,
						React.createElement("tr", null, React.createElement("th", {colSpan: "2"}, "General info"))
					),
					React.createElement("tbody", null,
						React.createElement("tr", null, React.createElement("td", null, "Zend OPcache"), React.createElement("td", null, this.state.version.version)),
						React.createElement("tr", null, React.createElement("td", null, "PHP"), React.createElement("td", null, this.state.version.php)),
						React.createElement("tr", null, React.createElement("td", null, "Host"), React.createElement("td", null, this.state.version.host)),
						React.createElement("tr", null, React.createElement("td", null, "Server Software"), React.createElement("td", null, this.state.version.server)),
						React.createElement("tr", null, React.createElement("td", null, "Start time"), React.createElement("td", null, this.state.start)),
						React.createElement("tr", null, React.createElement("td", null, "Last reset"), React.createElement("td", null, this.state.reset))
					)
				)
			);
		}
	});

	var Directives = React.createClass({displayName: "Directives",
		getInitialState: function() {
			return { data : opstate.directives };
		},
		render: function() {
			var directiveNodes = this.state.data.map(function(directive) {
				var map = { 'opcache.':'', '_':' ' };
				var dShow = directive.k.replace(/opcache\.|_/gi, function(matched){
					return map[matched];
				});
				var vShow;
				if (directive.v === true || directive.v === false) {
					vShow = React.createElement('i', {}, directive.v.toString());
				} else if (directive.v === '') {
					vShow = React.createElement('i', {}, 'no value');
				} else {
					vShow = directive.v;
				}
				return (
					React.createElement("tr", {key: directive.k},
						React.createElement("td", {title: 'View ' + directive.k + ' manual entry'}, React.createElement("a", {href: 'http://php.net/manual/en/opcache.configuration.php#ini.'
						+ (directive.k).replace(/_/g,'-'), target: "_blank"}, dShow)),
						React.createElement("td", null, vShow)
					)
				);
			});
			return (
				React.createElement("table", null,
					React.createElement("thead", null,
						React.createElement("tr", null, React.createElement("th", {colSpan: "2"}, "Directives"))
					),
					React.createElement("tbody", null, directiveNodes)
				)
			);
		}
	});

	var Files = React.createClass({displayName: "Files",
		getInitialState: function() {
			return {
				data : opstate.files,
				showing: null,
				allowFiles: allowFiles
			};
		},
		handleInvalidate: function(e) {
			e.preventDefault();
			if (realtime) {
				$.get('#', { invalidate: e.currentTarget.getAttribute('data-file') }, function(data) {
					console.log('success: ' + data.success);
				}, 'json');
			} else {
				window.location.href = e.currentTarget.href;
			}
		},
		render: function() {
			if (this.state.allowFiles) {
				var fileNodes = this.state.data.map(function(file, i) {
					var invalidate, invalidated;
					if (file.timestamp == 0) {
						invalidated = React.createElement("span", null, React.createElement("i", {className: "invalid metainfo"}, " - has been invalidated"));
					}
					if (canInvalidate) {
						invalidate = React.createElement("span", null, ", ", React.createElement("a", {className: "metainfo", href: '?invalidate='
						+ file.full_path, "data-file": file.full_path, onClick: this.handleInvalidate}, "force file invalidation"));
					}
					return (
						React.createElement("tr", {key: file.full_path, "data-path": file.full_path.toLowerCase(), className: i%2?'alternate':''},
							React.createElement("td", null,
								React.createElement("div", null,
									React.createElement("span", {className: "pathname"}, file.full_path), React.createElement("br", null),
									React.createElement(FilesMeta, {data: [file.readable.hits, file.readable.memory_consumption, file.last_used]}),
									invalidate,
									invalidated
								)
							)
						)
					);
				}.bind(this));
				return (
					React.createElement("div", null,
						React.createElement(FilesListed, {showing: this.state.showing}),
						React.createElement("table", null,
							React.createElement("thead", null,
								React.createElement("tr", null,
									React.createElement("th", null, "Script")
								)
							),
							React.createElement("tbody", null, fileNodes)
						)
					)
				);
			} else {
				return React.createElement("span", null);
			}
		}
	});

	var FilesMeta = React.createClass({displayName: "FilesMeta",
		render: function() {
			return (
				React.createElement("span", {className: "metainfo"},
					React.createElement("b", null, "hits: "), React.createElement("span", null, this.props.data[0], ", "),
					React.createElement("b", null, "memory: "), React.createElement("span", null, this.props.data[1], ", "),
					React.createElement("b", null, "last used: "), React.createElement("span", null, this.props.data[2])
				)
			);
		}
	});

	var FilesListed = React.createClass({displayName: "FilesListed",
		getInitialState: function() {
			return {
				formatted : opstate.overview.readable.num_cached_scripts,
				total     : opstate.overview.num_cached_scripts
			};
		},
		render: function() {
			var display = this.state.formatted + ' file' + (this.state.total == 1 ? '' : 's') + ' cached';
			if (this.props.showing !== null && this.props.showing != this.state.total) {
				display += ', ' + this.props.showing + ' showing due to filter';
			}
			return (React.createElement("h3", null, display));
		}
	});

	var overviewCountsObj = ReactDOM.render(React.createElement(OverviewCounts, null), document.getElementById('counts'));
	var generalInfoObj = ReactDOM.render(React.createElement(GeneralInfo, null), document.getElementById('generalInfo'));
	var filesObj = ReactDOM.render(React.createElement(Files, null), document.getElementById('filelist'));
	ReactDOM.render(React.createElement(Directives, null), document.getElementById('directives'));
</script>

</body>
</html>
