PageRenderTime 291ms CodeModel.GetById 110ms app.highlight 77ms RepoModel.GetById 94ms app.codeStats 1ms

/global.php

http://showslow.googlecode.com/
PHP | 860 lines | 662 code | 113 blank | 85 comment | 91 complexity | c1b1526279db7227571205444c7361ec MD5 | raw file
  1<?php 
  2# change it if you want to allow other profiles including your custom profiles
  3$YSlow2AllowedProfiles = array('ydefault');
  4
  5# If not false, then should be an array of prefix matches - if one of them matches, URL will be accepted
  6$limitURLs = false;
  7
  8# Track non-http(s) URLs. Disabled by default
  9$enableNonHTTPURLs = false;
 10
 11# URL groups to be displayed on URLs measured tab
 12$URLGroups = array();
 13
 14# Ignore URLs matching the prefix or a regext. If one of them matches, URLs is going to be ignored
 15# You might want to remove 10.x, 192.168.x and 172.16-32.x if you're testing web sites on a private network.
 16$ignoreURLs = array(
 17	'http://0.0.0.0',
 18	'http://127.0.0.',
 19	'http://localhost/',
 20	'http://localhost:',
 21	'http://10.',
 22	'http://192.168.',
 23	'http://172.16.',
 24	'http://172.17.',
 25	'http://172.18.',
 26	'http://172.19.',
 27	'http://172.20.',
 28	'http://172.21.',
 29	'http://172.22.',
 30	'http://172.23.',
 31	'http://172.24.',
 32	'http://172.25.',
 33	'http://172.26.',
 34	'http://172.27.',
 35	'http://172.28.',
 36	'http://172.29.',
 37	'http://172.30.',
 38	'http://172.31.'
 39);
 40
 41# If set to true, drop all query strings. If array, then match prefixes.
 42$dropQueryStrings = false;
 43
 44# Custom metrics array
 45$metrics = array();
 46
 47# to see if your users are visiting the tool, enable Google Analytics
 48# (for publicly hosted instances)
 49$googleAnalyticsProfile = '';
 50
 51# KissMetrics key
 52$kissMetricsKey = '';
 53
 54# show Feedback button
 55$showFeedbackButton = true;
 56
 57# AddThis profile, set it to enable sharing functions
 58$addThisProfile = null;
 59
 60# how old should data be for deletion (in days)
 61# anything >0 will delete old data
 62# don't forget to add a cron job to run deleteolddata.php
 63$oldDataInterval = 60;
 64
 65# Enable this if you'd like to clean old yslow beacon details to conserve space
 66# (beacon details are currently only used for tooltips for latest yslow breakdown)
 67$cleanOldYSlowBeaconDetails = false;
 68
 69$homePageMetaTags = '';
 70
 71# this enables a form to run a test on WebPageTest.org
 72$webPageTestKey = null; # must be set to something to not null to enable
 73$webPageTestBase = 'http://www.webpagetest.org/';
 74$webPageTestPrivateByDefault = false;
 75$webPageTestFirstRunOnlyByDefault = false;
 76$webPageTestExtraParams = '';
 77$keepPrivatePageTests = false;
 78
 79# a list of URLs to compare by default. Set to NULL to not send any URLs
 80# $defaultURLsToCompare = array('http://www.google.com/', 'http://www.yahoo.com/', 'http://www.amazon.com/');
 81$defaultURLsToCompare = NULL;
 82
 83# Change this to 'pagespeed' to use it for comparison by default
 84$defaultRankerToCompare = 'yslow';
 85
 86# Enabling HAR beacon will allow storing HAR data for URLs and display graphically using HAR viewer
 87$enableHARBeacon = false;
 88
 89# HAR Viewer base URL
 90$HARViewerBase = 'http://www.softwareishard.com/har/viewer/';
 91
 92# Enable user URL monitoring
 93$enableMyURLs = false;
 94
 95# Maximum URLs each user can add to the system to be monitored (false means no limit)
 96$maxURLsPerUser = false;
 97
 98# Message to show the user when he riches the maximum
 99$maxURLsMessage = 'The number of URLs tracked is limited because of load constraints.';
100
101# Privileged users who has no limit on URLs
102$noMaxURLsForUsers = array();
103
104# how long should monitoring scripts wait between measurements (in hours).
105$monitoringPeriod = 24;
106
107# Facebook connect properties, configure them here:
108# http://www.facebook.com/developers/createapp.php
109$facebookAPIKey = null;
110$facebookSecret = null;
111
112# Google Friend connect site ID
113# get it from the URL's "id" parameter on Google Friend Connect admin page for the site:
114# http://www.google.com/friendconnect/admin/
115$googleFriendConnectSiteID = null;
116
117# Smoothing distance (averaging window will be from x-distance to x+distance)
118$smoothDistance = 5;
119
120require_once(dirname(__FILE__).'/asset_versions.php');
121require_once(dirname(__FILE__).'/svn-assets/asset_functions.php');
122
123# Put description for ShowSlow instance into this variable - it'll be displayed on home page under the header.
124$ShowSlowIntro = '<p>Show Slow is an open source tool that helps monitor various website performance metrics over time. It captures the results of <a href="http://developer.yahoo.com/yslow/">YSlow</a> and <a href="http://code.google.com/speed/page-speed/">Page Speed</a> rankings and graphs them, to help you understand how various changes to your site affect its performance.</p>
125
126<p><a href="http://www.showslow.com/">www.ShowSlow.com</a> is a demonstration site that continuously measures the performance of a few reference web pages. It also allows for public metrics reporting.</p>
127
128<p>If you want to make your measurements publicly available on this page, see the instructions in <a href="configure.php">Configuring YSlow / Page Speed</a>. If you want to keep your measurements private, <b><a href="http://code.google.com/p/showslow/source/checkout">download Show Slow</a></b> from the SVN repository and install it on your own server.</p>
129
130<p>You can ask questions and discuss ShowSlow in our group <a href="http://groups.google.com/group/showslow">http://groups.google.com/group/showslow</a> or just leave feedback at <a href="http://showslow.uservoice.com">http://showslow.uservoice.com</a></p>
131
132<table cellpadding="0" cellspacing="0" border="0"><tr>
133<td valign="top"><style>
134#twitterbutton {
135	margin-right: 7px;
136	width: 58px;
137	height: 23px;
138	display: block;
139	background-image: url('.assetURL('follow.png').');
140	background-position: 0px 0px;
141}
142#twitterbutton:hover {
143	background-position: 0px -46px;
144}
145</style><a id="twitterbutton" href="http://twitter.com/showslow" target="_blank" title="follow @showslow on twitter"/></a></td>
146<td valign="top">
147<iframe src="http://www.facebook.com/plugins/like.php?href=http%253A%252F%252Fwww.showslow.com%252F&amp;layout=standard&amp;show_faces=false&amp;width=450&amp;action=recommend&amp;font&amp;colorscheme=light&amp;height=35" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:450px; height:23px;" allowTransparency="true"></iframe>
148</td>
149</tr></table>';
150
151# configuring tabs
152$customLists = array();
153
154# additional menu items (url, title are keys for each item) 
155$additionalMenuItems = array();
156
157# config will override defaults above
158require_once(dirname(__FILE__).'/config.php');
159
160# PUT ALL THINGS THAT SHOULDN'T BE CONFIGURABLE BELOW THIS LINE
161
162# metric type constants
163define('BYTES', 0);
164define('PERCENTS', 1);
165define('MS', 2);
166define('NUMBER', 3);
167
168# used for legend (in parenthesis)
169# if no label needed like for number, just don't insert it here
170$metric_types = array(
171	BYTES =>	array( 'legend'	=> 'in bytes',	'units'	=> ' bytes'),
172	MS =>		array( 'legend'	=> 'im ms',	'units'	=> ' ms'),
173	PERCENTS =>	array( 'legend'	=> '0-100',	'units'	=> '%'),
174	NUMBER =>	array( 'legend'	=> '',		'units'	=> '')
175);
176
177# a list of metrics (excluding custom metrics) available to be displayed on the graph
178$all_metrics = array(
179	'yslow' => array(
180		'title' => 'YSlow',
181		'url' => 'http://developer.yahoo.com/yslow/',
182		'table' => 'yslow2',
183		'score_name' => 'grade',
184		'score_column' => 'o',
185		'metrics' => array(
186			'Basic measurements' => array( 
187				array( 'Overall rank',					'o',		PERCENTS),
188				array( 'Page Size',					'w',		BYTES),
189				array( 'Amount of requests with empty cache',		'r',		NUMBER),
190				array( 'Page Load time',				'lt',		MS)
191			),
192			'Best practices' => array( 
193				array( 'Make fewer HTTP requests',		'ynumreq',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#num_http'),
194				array( 'Use a Content Delivery Network (CDN)',	'ycdn', 	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#cdn'),
195				array( 'Add Expires headers',			'yexpires',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#expires'),
196				array( 'Avoid Empty Image src',			'yemptysrc',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#emptysrc'),
197				array( 'Compress components with gzip',		'ycompress',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#gzip'),
198				array( 'Put CSS at top',			'ycsstop',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#css_top'),
199				array( 'Put JavaScript at bottom',		'yjsbottom',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#js_bottom'),
200				array( 'Avoid CSS expressions',			'yexpressions',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#css_expressions'),
201				array( 'Make JavaScript and CSS external',	'yexternal',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#external'),
202				array( 'Reduce DNS lookups',			'ydns',		PERCENTS,	'http://developer.yahoo.com/performance/rules.html#dns_lookups'),
203				array( 'Minify JavaScript and CSS',		'yminify',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#minify'),
204				array( 'Avoid URL redirects',			'yredirects',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#redirects'),
205				array( 'Remove duplicate JavaScript and CSS',	'ydupes',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#js_dupes'),
206				array( 'Configure entity tags (ETags)',		'yetags',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#etags'),
207				array( 'Make AJAX cacheable',			'yxhr',		PERCENTS,	'http://developer.yahoo.com/performance/rules.html#cacheajax'),
208				array( 'Use GET for AJAX requests',		'yxhrmethod',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#ajax_get'),
209				array( 'Reduce the number of DOM elements',	'ymindom',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#min_dom'),
210				array( 'Avoid HTTP 404 (Not Found) error',	'yno404',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#no404'),
211				array( 'Reduce cookie size',			'ymincookie',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#cookie_size'),
212				array( 'Use cookie-free domains',		'ycookiefree',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#cookie_free'),
213				array( 'Avoid AlphaImageLoader filter',		'ynofilter',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#no_filters'),
214				array( 'Do not scale images in HTML',		'yimgnoscale',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#no_scale'),
215				array( 'Make favicon small and cacheable',	'yfavicon',	PERCENTS,	'http://developer.yahoo.com/performance/rules.html#favicon')
216			)
217		)
218	),
219	'pagespeed' => array(
220		'title' => 'Page Speed',
221		'url' => 'http://code.google.com/speed/page-speed/',
222		'table' => 'pagespeed',
223		'score_name' => 'score',
224		'score_column' => 'o',
225		'metrics' => array(
226			'Basic measurements' => array(
227				array( 'Page size',				'w',	BYTES),
228				array( 'Page load time',			'l',	MS),
229				array( 'Transfer size of all resources',	't',	BYTES),
230				array( 'Total Requests',			'r',	NUMBER),
231				array( 'Overall grade',				'o',	PERCENTS)
232			),
233			'Optimize caching' => array(
234				array( 'Leverage browser caching',	'pBrowserCache',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/caching.html#LeverageBrowserCaching'),
235				array( 'Leverage proxy caching',	'pCacheValid',		PERCENTS,	'http://code.google.com/speed/page-speed/docs/caching.html#LeverageProxyCaching'),
236			),
237			'Minimize round-trip times' => array(
238				array( 'Minimize DNS lookups',				'pMinDns',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rtt.html#MinimizeDNSLookups'),
239				array( 'Minimize redirects',				'pMinRedirect',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rtt.html#AvoidRedirects'),
240				array( 'Avoid bad requests',				'pBadReqs',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rtt.html#AvoidBadRequests'),
241				array( 'Combine external JavaScript',			'pCombineJS',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rtt.html#CombineExternalJS'),
242				array( 'Combine external CSS',				'pCombineCSS',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rtt.html#CombineExternalCSS'),
243				array( 'Combine images using CSS sprites',		'pSprite',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rtt.html#SpriteImages'),
244				array( 'Optimize the order of styles and scripts',	'pCssJsOrder',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rtt.html#PutStylesBeforeScripts'),
245				array( 'Avoid document.write',				'pDocWrite',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rtt.html#AvoidDocumentWrite'),
246				array( 'Avoid CSS @import',				'pCssImport',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rtt.html#AvoidCssImport'),
247				array( 'Prefer asynchronous resources',			'pPreferAsync',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rtt.html#PreferAsyncResources'),
248				array( 'Parallelize downloads across hostnames',	'pParallelDl',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rtt.html#ParallelizeDownloads')
249			),
250			'Minimize request overhead' => array(
251				array( 'Minimize request size',				'pMinReqSize',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/request.html#MinimizeRequestSize'),
252				array( 'Serve static content from a cookieless domain',	'pNoCookie',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/request.html#ServeFromCookielessDomain')
253			),
254			'Minimize payload size' => array(
255				array( 'Enable compression',			'pGzip',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/payload.html#GzipCompression'),
256				array( 'Remove unused CSS',			'pUnusedCSS',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/payload.html#RemoveUnusedCSS'),
257				array( 'Minify JavaScript',			'pMinifyJS',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/payload.html#MinifyJS'),
258				array( 'Minify CSS',				'pMinifyCSS',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/payload.html#MinifyCSS'),
259				array( 'Minify HTML',				'pMinifyHTML',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/payload.html#MinifyHTML'),
260				array( 'Defer loading of JavaScript',		'pDeferJS',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/payload.html#DeferLoadingJS'),
261				array( 'Optimize images',			'pOptImgs',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/payload.html#CompressImages'),
262				array( 'Serve scaled images',			'pScaleImgs',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/payload.html#ScaleImages'),
263				array( 'Serve resources from a consistent URL',	'pDupeRsrc',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/payload.html#duplicate_resources')
264			),
265			'Optimize browser rendering' => array(
266				array( 'Use efficient CSS selectors',	'pCssSelect',		PERCENTS,	'http://code.google.com/speed/page-speed/docs/rendering.html#UseEfficientCSSSelectors'),
267				array( 'Put CSS in the document head',	'pCssInHead',		PERCENTS,	'http://code.google.com/speed/page-speed/docs/rendering.html#PutCSSInHead'),
268				array( 'Specify image dimensions',	'pImgDims',		PERCENTS,	'http://code.google.com/speed/page-speed/docs/rendering.html#SpecifyImageDimensions'),
269				array( 'Specify a character set early',	'pCharsetEarly',	PERCENTS,	'http://code.google.com/speed/page-speed/docs/rendering.html#SpecifyCharsetEarly'),
270			)
271		)
272	),
273	'dynatrace' => array(
274		'title' => 'dynaTrace',
275		'url' => 'http://ajax.dynatrace.com/',
276		'table' => 'dynatrace',
277		'score_name' => 'rank',
278		'score_column' => 'rank',
279		'metrics' => array(
280			'Event times' => array(
281				array( 'Time to first impression',	'timetoimpression',	MS),
282				array( 'Time to onLoad',		'timetoonload',		MS),
283				array( 'Time to full page load',	'timetofullload',	MS)
284			),
285			'Total time breakdown' => array(
286				array( 'Total time on network',		'timeonnetwork',	MS),
287				array( 'Total time in JavaScript',	'timeinjs',		MS),
288				array( 'Total time in rendering',	'timeinrendering',	MS)
289			),
290			'Requests and size' => array(
291				array( 'Number of requests',		'reqnumber',		NUMBER),
292				array( 'Number of XHR requests',	'xhrnumber',		NUMBER),
293				array( 'Total page size',		'pagesize',		BYTES),
294				array( 'Total cachable size',		'cachablesize',		BYTES),
295				array( 'Total non-cachable size',	'noncachablesize',	BYTES)
296			),
297			'Best practices' => array(
298				array( 'Overall rank',			'rank',		PERCENTS),
299				array( 'Caching rank',			'cache',	PERCENTS,	'https://community.dynatrace.com/community/display/PUB/Best+Practices+on+Browser+Caching'),
300				array( 'Network rank',			'net',		PERCENTS,	'https://community.dynatrace.com/community/display/PUB/Best+Practices+on+Network+Requests+and+Roundtrips'),
301				array( 'Server rank',			'server',	PERCENTS,	'https://community.dynatrace.com/community/display/PUB/Best+Practices+on+Server-Side+Performance+Optimization'),
302				array( 'JavaScript rank',		'js',		PERCENTS,	'https://community.dynatrace.com/community/display/PUB/Best+Practices+on+JavaScript+and+AJAX+Performance')
303			)
304		)
305	)
306);
307
308function prettyScore($num) {
309	$letter = 'F';
310
311	if ( 90 <= $num )
312		$letter = 'A';
313	else if ( 80 <= $num )
314		$letter = 'B';
315	else if ( 70 <= $num )
316		$letter = 'C';
317	else if ( 60 <= $num )
318		$letter = 'D';
319	else if ( 50 <= $num )
320		$letter = 'E';
321
322	return $letter;
323}
324
325function scoreColorStep($num, $total = 13) {
326	for($i=1; $i<=$total; $i++)
327	{
328		if ($num <= $i*100/$total)
329		{
330			return $i;
331		} 
332	}
333}
334
335$colorSteps = array(
336	'EE0000',
337	'EE2800',
338	'EE4F00',
339	'EE7700',
340	'EE9F00',
341	'EEC600',
342	'EEEE00',
343	'C6EE00',
344	'9FEE00',
345	'77EE00',
346	'4FEE00',
347	'28EE00',
348	'00EE00'
349);
350
351$colorStepShades = array(
352	'CF0000',
353	'CF2200',
354	'CF4400',
355	'CF6600',
356	'CF8800',
357	'CFAA00',
358	'CDCF00',
359	'ABCF00',
360	'89CF00',
361	'67CF00',
362	'45CF00',
363	'23CF00',
364	'01CF00'
365);
366
367function scoreColor($num, $darker = false) {
368	global $colorSteps, $colorStepShades;
369
370	$colors = $darker ? $colorStepShades : $colorSteps;
371	
372	return '#'.$colors[scoreColorStep($num, count($colors))-1];
373}
374
375# returns true if URL should be ignored
376function isURLIgnored($url) {
377	global $ignoreURLs;
378
379	if ($ignoreURLs !== false && is_array($ignoreURLs)) {
380		$matched = false;
381
382		foreach ($ignoreURLs as $ignoreString) {
383			// checking if string is a regex or just a prefix
384			if (preg_match('/^[^a-zA-Z\\\s]/', $ignoreString))
385			{
386				if (preg_match($ignoreString, $url)) {
387					$matched = true;
388				}
389			} else if (substr($url, 0, strlen($ignoreString)) == $ignoreString) {
390				$matched = true;
391				break;
392			}
393		}
394
395		return $matched;
396	}
397
398	return false;
399}
400
401# returns true if URL is in the limitedURLs array or all URLs are allowed
402function isURLAllowed($url) {
403	global $limitURLs;
404
405	if ($limitURLs !== false && is_array($limitURLs)) {
406		$matched = false;
407
408		foreach ($limitURLs as $limitString) {
409			// checking if string is a regex or just a prefix
410			if (preg_match('/^[^a-zA-Z\\\s]/', $limitString))
411			{
412				if (preg_match($limitString, $url)) {
413					$matched = true;
414				}
415			} else if (substr($url, 0, strlen($limitString)) == $limitString) {
416				$matched = true;
417				break;
418			}
419		}
420
421		return $matched;
422	}
423	return true;
424}
425
426# returns true if this URLS is not an HTTP URL and should be ignored
427function shouldBeIgnoredAsNonHTTP($url) {
428	global $enableNonHTTPURLs;
429
430	return (
431		!$enableNonHTTPURLs &&
432		(substr(strtolower($url), 0, 7) != 'http://' &&	substr(strtolower($url), 0, 8) != 'https://')
433	);
434}
435
436# returns URL if it's valid and passes all checks or null if not.
437# if second parameter is true (default), then 404 error page is shown
438#
439# used in getUrlId and event beacon (which tests prefix, not URL)
440#
441# TODO rewrite to use exceptions instead of $outputerror contraption
442function validateURL($url, $outputerror = true) {
443	$url = filter_var(urldecode(trim($url)), FILTER_VALIDATE_URL);
444
445	if ($url === FALSE) {
446		if (!$outputerror) {
447			return null;
448		}
449
450		header('HTTP/1.0 400 Bad Request');
451
452		?><html>
453<head>
454<title>Bad Request: ShowSlow beacon</title>
455</head>
456<body>
457<h1>Bad Request: ShowSlow beacon</h1>
458<p>Invalid URL submitted.</p>
459</body></html>
460<?php 
461		exit;
462	}
463
464	if (shouldBeIgnoredAsNonHTTP($url)) {
465		if (!$outputerror) {
466			return null;
467		}
468
469		header('HTTP/1.0 400 Bad Request');
470
471		?><html>
472<head>
473<title>Bad Request: ShowSlow beacon</title>
474</head>
475<body>
476<h1>Bad Request: ShowSlow beacon</h1>
477<p>This instance of Show Slow only tracks HTTP(S) URLs.</p>
478</body></html>
479<?php 
480		exit;
481	}
482
483	if (isURLIgnored($url)) {
484		if (!$outputerror) {
485			return null;
486		}
487
488		header('HTTP/1.0 400 Bad Request');
489
490		?><html>
491<head>
492<title>Bad Request: ShowSlow beacon</title>
493</head>
494<body>
495<h1>Bad Request: ShowSlow beacon</h1>
496<p>This URL matched ignore list for this instance of Show Slow.</p>
497</body></html>
498<?php 
499		exit;
500	}
501
502	if (!isURLAllowed($url)) {
503		if (!$outputerror) {
504			return null;
505		}
506
507		header('HTTP/1.0 400 Bad Request');
508
509		?><html>
510<head>
511<title>Bad Request: ShowSlow beacon</title>
512</head>
513<body>
514<h1>Bad Request: ShowSlow beacon</h1>
515<p>URL doesn't match any URLs allowed allowed to this instance.</p>
516</body></html>
517<?php 
518		exit;
519	}
520
521	return $url;
522}
523
524$webPageTestLocations = array();
525$webPageTestLocationsById = array();
526function getPageTestLocations() {
527	global $webPageTestLocations, $webPageTestLocationsById, $webPageTestBase, $webPageTestKey;
528
529	if (count($webPageTestLocations) > 0 || is_null($webPageTestKey)) {
530		return;
531	}
532
533	// Getting a list of locations from WebPageTest
534	$ch = curl_init(); 
535	curl_setopt($ch, CURLOPT_URL, $webPageTestBase.'getLocations.php?f=xml&k='.urlencode($webPageTestKey));
536	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
537	$output = curl_exec($ch);
538
539	if (empty($output)) {
540		$err = curl_error($ch);
541		curl_close($ch);
542		failWithMessage("API call ($locationsURL) failed: ".$err);
543	}
544
545	$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
546	if ($code != 200) {
547		curl_close($ch);
548		failWithMessage("PageTest didn't accept the request: $code");
549	}
550	curl_close($ch);
551
552	$xml = new SimpleXMLElement($output);
553
554	if (empty($xml)) {
555		failWithMessage("Failed to parse XML response");
556	}
557
558	if ($xml->statusCode != 200) {
559		failWithMessage("PageTest getLocations returned failure status code: ".$xml->statusCode." (".$xml->statusText.")");
560	}
561	foreach ($xml->data->location as $location) {
562		$id = $location->id;
563
564		$loc = array(
565			'id' => $id,
566			'default' => $location->default == 1 ? true : false,
567			'title' => $location->Label.' ('.$location->Browser.')',
568			'tests' => $location->PendingTests->Total
569		);
570
571		$webPageTestLocations[] = $loc;
572		$webPageTestLocationsById["$id"] = $loc;
573	}
574}
575
576function getUrlId($url, $outputerror = true)
577{
578	global $dropQueryStrings;
579
580	$url = validateURL($url, $outputerror);
581
582	if (is_null($url)) {
583		return null;
584	}
585
586	if ($dropQueryStrings) {
587		$drop = false;
588
589		if (is_array($dropQueryStrings)) {
590			foreach ($dropQueryStrings as $prefix) {
591				if (substr($url, 0, strlen($prefix)) == $prefix) {
592					$drop = true;
593					break;
594				}
595			}
596		} else {
597			$drop = true;
598		}
599
600		if ($drop) {
601			$querypos = strpos($url, '?');
602
603			if ($querypos !== false) {
604				$url = substr($url, 0, $querypos);
605			}
606		}
607	}
608
609	# get URL id
610	$query = sprintf("SELECT id FROM urls WHERE url = '%s'", mysql_real_escape_string($url));
611	$result = mysql_query($query);
612
613	if (!$result) {
614		beaconError(mysql_error());
615	}
616
617	if (mysql_num_rows($result) == 1) {
618		$row = mysql_fetch_assoc($result);
619		return $row['id'];
620	} else if (mysql_num_rows($result) == 0) {
621		// Emulating unique index on a blob with unlimited length by locking the table on write
622		// locking only when we're about to insert so we don't block the whole thing on every read
623
624		// locking the table to make sure we pass it only by one concurrent process
625		$result = mysql_query('LOCK TABLES urls WRITE');
626		if (!$result) {
627			beaconError(mysql_error());
628		}
629
630		// selecting the URL again to make sure there was no concurrent insert for this URL
631		$query = sprintf("SELECT id FROM urls WHERE url = '%s'", mysql_real_escape_string($url));
632		$result = mysql_query($query);
633		if (!$result) {
634			$mysql_err = mysql_error();
635			mysql_query('UNLOCK TABLES'); // unlocking the table if in trouble
636			beaconError($mysql_err);
637		}
638
639		// repeating the check
640		if (mysql_num_rows($result) == 1) {
641			$row = mysql_fetch_assoc($result);
642			$url_id = $row['id'];
643		} else if (mysql_num_rows($result) == 0) {
644			$query = sprintf("INSERT INTO urls (url) VALUES ('%s')", mysql_real_escape_string($url));
645			$result = mysql_query($query);
646			if (!$result) {
647				$mysql_err = mysql_error();
648				mysql_query('UNLOCK TABLES'); // unlocking the table if in trouble
649				beaconError($mysql_err);
650			}
651
652			$url_id = mysql_insert_id();
653		} else if (mysql_num_rows($result) > 1) {
654			mysql_query('UNLOCK TABLES'); // unlocking the table if in trouble
655			beaconError('more then one entry found for the URL (when lock is aquired)');
656		}
657
658		$result = mysql_query('UNLOCK TABLES'); // now concurrent thread can try reading again
659		if (!$result) {
660			beaconError(mysql_error());
661		}
662
663		return $url_id;
664	} else {
665		beaconError('more then one entry found for the URL');
666	}
667}
668
669// httpd_build_url replacement from http://www.mediafire.com/?zjry3tynkg5
670// added base function feature that allows to pass an array as first parameter
671if (!function_exists('http_build_url'))
672{
673	define('HTTP_URL_REPLACE', 1);	// Replace every part of the first URL when there's one of the second URL
674	define('HTTP_URL_JOIN_PATH', 2); 	// Join relative paths
675	define('HTTP_URL_JOIN_QUERY', 4);	// Join query strings
676	define('HTTP_URL_STRIP_USER', 8);	// Strip any user authentication information
677	define('HTTP_URL_STRIP_PASS', 16);	// Strip any password authentication information
678	define('HTTP_URL_STRIP_AUTH', 32);	// Strip any authentication information
679	define('HTTP_URL_STRIP_PORT', 64);	// Strip explicit port numbers
680	define('HTTP_URL_STRIP_PATH', 128);	// Strip complete path
681	define('HTTP_URL_STRIP_QUERY', 256);	// Strip query string
682	define('HTTP_URL_STRIP_FRAGMENT', 512);	// Strip any fragments (#identifier)
683	define('HTTP_URL_STRIP_ALL', 1024);	// Strip anything but scheme and host
684	
685	// Build an URL
686	// The parts of the second URL will be merged into the first according to the flags argument. 
687	// 
688	// @param mixed	(Part(s) of) an URL in form of a string or associative array like parse_url() returns
689	// @param mixed	Same as the first argument
690	// @param int	A bitmask of binary or'ed HTTP_URL constants (Optional)HTTP_URL_REPLACE is the default
691	// @param array	If set, it will be filled with the parts of the composed url like parse_url() would return 
692	function http_build_url($url, $parts=array(), $flags=HTTP_URL_REPLACE, &$new_url=false)
693	{
694		$keys = array('user','pass','port','path','query','fragment');
695		
696		// HTTP_URL_STRIP_ALL becomes all the HTTP_URL_STRIP_Xs
697		if ($flags & HTTP_URL_STRIP_ALL)
698		{
699			$flags |= HTTP_URL_STRIP_USER;
700			$flags |= HTTP_URL_STRIP_PASS;
701			$flags |= HTTP_URL_STRIP_PORT;
702			$flags |= HTTP_URL_STRIP_PATH;
703			$flags |= HTTP_URL_STRIP_QUERY;
704			$flags |= HTTP_URL_STRIP_FRAGMENT;
705		}
706		// HTTP_URL_STRIP_AUTH becomes HTTP_URL_STRIP_USER and HTTP_URL_STRIP_PASS
707		else if ($flags & HTTP_URL_STRIP_AUTH)
708		{
709			$flags |= HTTP_URL_STRIP_USER;
710			$flags |= HTTP_URL_STRIP_PASS;
711		}
712		
713		// Parse the original URL
714		if (is_array($url)) {
715			$parse_url = $url;
716		} else {
717			$parse_url = parse_url($url);
718		}
719		
720		// Scheme and Host are always replaced
721		if (isset($parts['scheme']))
722			$parse_url['scheme'] = $parts['scheme'];
723		if (isset($parts['host']))
724			$parse_url['host'] = $parts['host'];
725		
726		// (If applicable) Replace the original URL with it's new parts
727		if ($flags & HTTP_URL_REPLACE)
728		{
729			foreach ($keys as $key)
730			{
731				if (isset($parts[$key]))
732					$parse_url[$key] = $parts[$key];
733			}
734		}
735		else
736		{
737			// Join the original URL path with the new path
738			if (isset($parts['path']) && ($flags & HTTP_URL_JOIN_PATH))
739			{
740				if (isset($parse_url['path']))
741					$parse_url['path'] = rtrim(str_replace(basename($parse_url['path']), '', $parse_url['path']), '/') . '/' . ltrim($parts['path'], '/');
742				else
743					$parse_url['path'] = $parts['path'];
744			}
745			
746			// Join the original query string with the new query string
747			if (isset($parts['query']) && ($flags & HTTP_URL_JOIN_QUERY))
748			{
749				if (isset($parse_url['query']))
750					$parse_url['query'] .= '&' . $parts['query'];
751				else
752					$parse_url['query'] = $parts['query'];
753			}
754		}
755
756			
757		// Strips all the applicable sections of the URL
758		// Note: Scheme and Host are never stripped
759		foreach ($keys as $key)
760		{
761			if ($flags & (int)constant('HTTP_URL_STRIP_' . strtoupper($key)))
762				unset($parse_url[$key]);
763		}
764		
765		$new_url = $parse_url;
766		
767		return 
768			 ((isset($parse_url['scheme'])) ? $parse_url['scheme'] . '://' : '')
769			.((isset($parse_url['user'])) ? $parse_url['user'] . ((isset($parse_url['pass'])) ? ':' . $parse_url['pass'] : '') .'@' : '')
770			.((isset($parse_url['host'])) ? $parse_url['host'] : '')
771			.((isset($parse_url['port'])) ? ':' . $parse_url['port'] : '')
772			.((isset($parse_url['path'])) ? $parse_url['path'] : '')
773			.((isset($parse_url['query'])) ? '?' . $parse_url['query'] : '')
774			.((isset($parse_url['fragment'])) ? '#' . $parse_url['fragment'] : '')
775		;
776	}
777}
778
779function resolveRedirects($url) {
780	if (function_exists('curl_init')) {
781		$ch = curl_init($url);
782
783		curl_setopt_array($ch, array(
784			CURLOPT_NOBODY => TRUE,
785			CURLOPT_FOLLOWLOCATION => TRUE,
786			CURLOPT_MAXREDIRS => 10
787		));
788
789		if (curl_exec($ch)) {
790			$new_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
791
792			# TODO also test for success code
793			# TODO maybe, fix www. when it's missing.
794
795			if ($new_url) {
796				$url = $new_url;
797			}
798		}
799	}
800
801	// now, let's fix trailing slash in case of domain-only request
802	$urlparts = parse_url($url);
803	if (!array_key_exists('path', $urlparts) || $urlparts['path'] == '') {
804		$urlparts['path'] = '/';
805	}
806
807	$new_url = http_build_url($urlparts);
808	if ($new_url) {
809		$url = $new_url;
810	}
811
812	return $url;
813}
814
815function failWithMessage($message)
816{
817	error_log("[Page Error] ".$message);
818	header('HTTP/1.0 500 ShowSlow Error');
819	?>
820<head>
821<title>500 ShowSlow Error</title>
822</head>
823<body>
824<h1>500 ShowSlow Error</h1>
825<p>Something went wrong. If it persists, please report it to <a href="http://code.google.com/p/showslow/issues/list">issue tracker</a>.</a>
826<p><?php echo $message?></p>
827</body></html>
828<?php
829	exit;
830}
831
832function beaconError($message)
833{
834	error_log($message);
835	header('HTTP/1.0 500 Beacon Error');
836	?>
837<head>
838<title>500 Beacon Error</title>
839</head>
840<body>
841<h1>500 BeaconError</h1>
842<p><?php echo $message?></p>
843</body></html>
844<?php
845	exit;
846}
847
848/*
849 * Cuts the string to be $maxlength and replaces the last $margin characters with ellipsis
850*/
851function ellipsis($string, $maxlength, $margin = 2) {
852	if (strlen($string) > ($maxlength)) {
853		return substr($string, 0, $maxlength - $margin)."...";
854	}
855
856	return $string;
857}
858
859mysql_connect($host, $user, $pass);
860mysql_select_db($db);