· 10 years ago · Aug 25, 2016, 08:14 PM
1<?php
2/*******************************************************************
3* Glype is copyright and trademark 2007-2016 UpsideOut, Inc. d/b/a Glype
4* and/or its licensors, successors and assigners. All rights reserved.
5*
6* Use of Glype is subject to the terms of the Software License Agreement.
7* http://www.glype.com/license.php
8*******************************************************************
9* This is a stand-alone admin control panel for the Glype software.
10******************************************************************/
11
12/*****************************************************************
13* Configuration - edit this section (if you want!)
14******************************************************************/
15
16# Path to the /includes/settings.php file. Change if you want to move
17# this admin script out of the glype directory. You can use a relative
18# or absolute path to the file.
19define('ADMIN_GLYPE_SETTINGS', 'includes/settings.php');
20
21# How long to keep an inactive admin session open for? After this
22# period of inactivity, an admin session is invalidated and you
23# must log in again. [seconds]
24define('ADMIN_TIMEOUT', 60*60);
25
26# Log viewer limit for collated stats. Limits "most viewed" to
27# the top X websites.
28define('ADMIN_STATS_LIMIT', 50);
29
30# End of configuration.
31
32
33/*****************************************************************
34* Initialize admin script
35******************************************************************/
36
37# Setup error reporting
38error_reporting(E_ALL);
39ini_set('display_errors', 1);
40
41# Define a path to us
42define('ADMIN_URI', $_SERVER['PHP_SELF']);
43
44# Define the current admin version
45define('ADMIN_VERSION', '1.4.15');
46
47# Start buffering
48ob_start();
49
50# Set up equivalents to glype's /includes/init.php constants
51# that might be available in the settings file
52define('GLYPE_URL', pathToURL(dirname(ADMIN_GLYPE_SETTINGS) . '/..'));
53define('GLYPE_ROOT', str_replace('\\', '/', dirname(dirname(realpath(ADMIN_GLYPE_SETTINGS)))));
54
55# And backwards compatibility (will be removed at some point)
56function findURL() { return GLYPE_URL; }
57define('LCNSE_KEY', '');
58define('proxyPATH', GLYPE_ROOT . '/');
59
60# Load current settings
61$settingsLoaded = file_exists(ADMIN_GLYPE_SETTINGS) && (@include ADMIN_GLYPE_SETTINGS);
62
63# Extract the "action" from the query string
64$action = isset($_SERVER['QUERY_STRING']) && preg_match('#^([a-z-]+)#', $_SERVER['QUERY_STRING'], $tmp) ? $tmp[1] : '';
65
66$cache_bust=filemtime(__FILE__)+filemtime(ADMIN_GLYPE_SETTINGS);
67
68# SHORTCUTS
69# Make a newline
70define('NL', "\r\n");
71
72/*****************************************************************
73* IMAGES (ugly but keeps it in a single file)
74******************************************************************/
75
76if ( isset($_GET['image']) ) {
77
78 # Send image function
79 function sendImage($str) {
80 header('Content-Type: image/gif');
81 header('Last-Modified: ' . gmdate("D, d M Y H:i:s", filemtime(__FILE__)) . 'GMT');
82 header('Expires: ' . gmdate("D, d M Y H:i:s", filemtime(__FILE__) + (60*60)) . 'GMT');
83 echo base64_decode($str);
84 exit;
85 }
86
87 switch ( $_GET['image'] ) {
88 case 'bg.gif':
89 sendImage('R0lGODlhkAMMAMQAAP////7+/v3+/fz9/Pr7+vn6+fj6+Pj5+PX39fL08u/x7+ru6ubq5uDm4OHm4dzi3Njf2NTc1NHZ0c7XzsnTycfRxwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAACQAwwAAAX/4CACZGmeaKoC4oEkysI0DxRJU1WsfO//wKBwSCwaj8ikcslsOp/QqHRKrVqv2KxWWKBIIpCHg7FQJBAGwwgrWldbr9isdsvttvi8fs/v+/+AgYKDhIWGSV1fYWNlZ2luVm0DV3AwMjQ2ODqHnJ2en6ChoqOkpaaAiWBiZGZoapNsbZQDLpZzmXanuru8vb6/wMHCo6mLrI6vWZKztXKYdZvD0tPU1dbX2NmCxauNrpBvspG0cZd0mnfa6uvs7e7v8KTcjK2PsLPgUpXO57nx/wADChxIsGCJece+3RuXL8o+c7iiGZxIsaLFixj/IPRmT5m4cM0gQkuXsaTJkyhTO6bcWC9ZrIZQHt4aqbKmzZs4c1ZjiQymw49UZD5Dp7Oo0aNIk/bhqdCjTydC+0lUSrWq1atYVTDtyCYEADs=');
90 break;
91 case 'bullet_green.gif':
92 sendImage('R0lGODlhEAAQAMQAAFOYS////6LUoJW4kI/Bi+nv6F28V5TSlLTcs5TMkYTKgp3VmlelUJvKluv26r/hv4zOhJ/cnJnMmV7CWev461WcTaPUoZa6kZrWlr3etYrMiQAAAAAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAAAQABAAAAUzYCCOZGmeaKqu7ElF07Q4aoQ9TyKp03NoFobKgNAoJBXVIiGQEC4qR6MCGBRa2Kx2qwoBADs=');
93 break;
94 case 'bullet_grey.gif':
95 sendImage('R0lGODlhEAAQALMAAHR0dLW1te/v76Wlpby8vI2NjcfHx62trXp6eszMzP///wAAAAAAAAAAAAAAAAAAACH5BAEHAAoALAAAAAAQABAAAAQtUMlJq704682vIEURCBoRJMkRaEUSDATCGscQAFpwmMOgCQcAYEDqGI/IZCYCADs=');
96 break;
97 case 'button.gif':
98 sendImage('R0lGODlhCgAoAKIAAOno6Ovq6/f39////+vp6vPx8uzr6u/v7yH5BAAHAP8ALAAAAAAKACgAAAMtOLos/jDKSWssOOvNOz5gKI6jYZ5oqq4m4b4EIM90bd94ru987//AoHBILBoTADs=');
99 break;
100 case 'content_bg.gif':
101 sendImage('R0lGODlhCAAyALMAAP////v9/fj7+/X6+vH4+O729ur09Ofy8uPw8N/v79zt7djr6wAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAAAIADIAAAQ8EMgpl7046827/5oijmJiniairurhvq4hz3Jh3zah7/rg/z6BcCgMGI9GinLJbDqf0Kh0Sq1ar9islhoBADs=');
102 break;
103 case 'footer.gif':
104 sendImage('R0lGODlhkAMwAOYAAP////7+/v39/f3+/fz9/Pz8/Pv7+/v8+/r7+vn6+fj6+Pf59/j5+Pb49vf49/b39vX39fT29PL18vP18/L08vH08fHz8e/y7/Dy8O7x7u/x7+3w7e3x7ezv7Ovv6+vu6+ru6unt6ejs6Ofr5+br5ubq5uTp5OTo5OPo4+Hn4eLn4uDm4OHm4d/l397k3t3j3dvi29zi3Nvh29rh2tng2drg2tjf2Nnf2dfe19ff19bd1tbe1tXd1dXc1dTc1NPb09La0tDZ0NHZ0c/Yz87Xzs3WzczWzMzVzMrUysvUy8nTycrTysjSyMnSycfRxwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAACQAzAAAAf/gASCAISFhoeIiYoAggwQFBogJSsxNj5CRE4Ji5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubqiCUpCPjYxLCUgGhQQCgqDuILLtY2PkZOVl5mbu9jZ2tvc3d7f4OHi4+Tl5qm9v8HDxcfJzrbNBLfQkJKUlpia5/z9/v8AAwocSLCgQXDpgAkjZgyZsnnMmtEj4MjetHzWDmrcyLGjx48gQ4ocmHAdQ3cPc8mbWFEavmr7RsqcSbOmzZs4c4orubCdQ3jPJMajGO0eNX3XdCpdyrSp06dQCfJk1/AdxIlAZdVzeTRj1K9gw4odS7Zsoaknf14dmjXWVqMY/2OanUu3rt27eL+h9WlVpdCgLeHCTJq3sOHDiBMn3ls1ZcS2sN5eHKy4suXLmDPXZIwSstu/tCS/RKq5tOnTqFN346zWr2dXorvKVU27tu3buBWx7vt47azYcQnnHk68uPG8ux1j9a2VqMXRXo9Ln069us7kr1+tHBp4Mmnr4MOLHy8QO/PfoNF3hz6bvPv38OPbMu/6fGTnXIPL38+/v/9Q9PXGUlHeRfffgQgm6F6Ayw34nGzCKSjhhBTexiBb9mmHn2DfVejhhyBedmFQ2bECHGUhpqjiinONGFp6za0HIYs01mjjUi6iV+IqJ3Z4449ABrlRjs3tqEqPBgqp5P+STJJD5GdGpoJke01WaeWVtzx5X5SoTBkhlmCGKaYpWmrI5SlejqnmmmxyUiZsMH4mo35t1mnnmG+2sh1gBLL35Z2ABvpjnibGed+cKAqq6KI0EsqjoRoi6iOjlFY6oaNHQgqnpEla6umn+2EqpaZ6blgglaCmqip4onZJaqGcorrqrLQO1yqarz4a65+19urrabeasueLu/5q7LGmBVvKsOr12RURSCDBxLROVGvttdhmq+223Hbr7bfghivuuOSWa+656Kar7rrstuvuu/DGK++89NZr77345quvt9P2e8QR6sSwQlq8NcgdBBJo8EEJLLxwAw9ADDFEEhRT3O//xRhnrPHGHHfs8ccghyzyyCSXbPLJKKes8sost+zyyzDHLPPMNNds880456xzyRQPAQQPN7zAAgkfXCABBAwwcKarSy9LQDISXODBCCq8QIMOPwQRhBFcG7HE12CHLfbYZJdt9tlop6322my37fbbcMct99x012333XjnrffefPft99+ABy744IS7TYQQQuhAwwsqjOCB0Q88YIABAdSHy+QNTIBBByKg4IIMNvQABBBEEPHv6ainrvrqrLfu+uuwxy777LTXbvvtuOeu++689+7778AHL/zwxBdv/PHIJ6/88rhr3YMNMriAgggdYDBBAw1MXrmAtxxwAPYWbBCC/wkrWE2DDz5oXfr67Lfv/vvwxy///PTXb//9+Oev//789+///wAMoAAHSMACGvCACEygAhfIwAY68IH8Gx0OcPCCFZggBBuwQAQikIAECEAAluveARwQgQpkQBIqcIELaLCDHfzgB4iLoQxnSMMa2vCGOMyhDnfIwx768IdADKIQh0jEIhrxiEhMohKXyMQmOvGJUIyiFKdIxSoKsQc9oEEMYqACYmSgAhFwgAO8tz2D2cJ7C1hA1DwgAhOwwAUwgIENbMADHqDvjnjMox73yMc++vGPgAykIAdJyEIa8pCITKQiF8nIRjrykZCMpCQnSclKWvKSmMykJjd5yDlukf8FJhDB446WNDKG0BYFKEDSJjAB8ZGABG/8nAxAN8cc2PKWuMylLnfJy1768pfADKYwh0nMYhrzmMhMpjKXycxmOvOZ0IymNKdJzWpa85rYzKY2t/nLOXozji5gwQlOgEELWCByCEDAB08ZlA4+4gIdCEEJSqACFbTgnlvMpz73yc9++vOfAA2oQAdK0IIa9KAITahCF8rQhjr0oRCNqEQnStGKWvSiGM2oRjfK0Y4q9J71LEEIOmA0CECgg6ksADtr4b2klbAC8ZRnCVBAU5qu4KY4zalOd8rTnvr0p0ANqlCHStSiGvWoSE2qUpfK1KY69alQjapUp0rVqlr1qli4zapWt8pVo9J0niPVgAY2WMoDBKCMGMpFKjv4AFZeQGoeYKMI5jrPutr1rnjNq173yte++vWvgA2sYAdL2MIa9rCITaxiF8vYxjr2sZCNrGQnS9nKWvaymM2sYUPAWQ5wwJyRQ2kqdcEslraUARs0ZwZWG1cPgOC1sI2tbGdL29ra9ra4za1ud8vb3vr2t8ANrnCHS9ziGve4yE2ucpfL3OY697nQja50p0td4G7guuYka1nNStpAAAA7');
105 break;
106 case 'footer_bg.gif':
107 sendImage('R0lGODlhMgAyAMQAAIXDKYTCKYPAKIPBKIK/KIG+KIG9KIC8J4C7J3+6J3+5J364Jn23Jn22Jny1JXu0JXqzJXmyJHmxJHevJHiwJHauIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAAAyADIAAAX/ICCOZGmeaKqubOu+cCzPdG3feK7vfO//wKCwFCgaj8ikcslsOp9Q5mBKrVqv2KzWKuh6v+CweEwum89oMmHNbrvf8Lh8Tq/b5YW8fs/v+/+AgYKDhIAGh4iJiouMjY6KB5GSk5SVlpeYmZqbnJgIn6ChoqOkpaanqKmqpgmtrq+wsbKztLAKt7i5uru8vb6/wMHCvgvFxsfIycrLzM3Oz9DMDNPU1dbX2Nna29zd3toN4eLj5OXm5+jkDuvs7e7v8PHy8/T19vIP+fr7/P3+/wADChxIsKDBgwUhKFzIsKHDhxAjSpxIsWLECBgzatzIsaPHjxsliBxJsqTJkyhTNKpcybJlSgowY8qcSbOmzZs4c+rceXOCz59AgwodSrSo0aNIkxatwLSp06dQo0qd+nTIixAAOw==');
108 break;
109 case 'nane.gif':
110 sendImage('R0lGODlhcQBVAPcAAP////7+/v39/fz8/Pv7+/r6+vn5+fj4+Pf39/b29vX19fT09PPz8/Ly8vHx8fDw8O/v7+7u7u3t7ezs7Ovr6+rq6unp6ejo6Ofn5+bm5uXl5eTk5OPj4+Li4uHh4eDg4N/f397e3t3d3dzc3Nvb29ra2tnZ2djY2NfX19bW1tXV1dTU1NPT09LS0tHR0dDQ0M/Pz87Ozs3NzczMzMvLy8rKysnJycjIyMfHx8bGxsXFxcTExMPDw8LCwsHBwcDAwL+/v76+vr29vby8vLu7u7q6urm5ubi4uLe3t7a2trW1tbS0tLOzs7KysrGxsbCwsK+vr66urq2traysrKurq6qqqqmpqaioqKenp6ampqWlpaSkpKOjo6KioqGhoaCgoJ+fn56enp2dnZycnJubm5qampmZmZiYmJeXl5aWlpWVlZSUlJOTk5KSkpGRkZCQkI+Pj46Ojo2NjYyMjIuLi4qKiomJiYiIiIeHh4aGhoWFhYSEhIODg4KCgoGBgYCAgH9/f35+fn19fXx8fHt7e3p6enl5eXh4eHd3d3Z2dnV1dXR0dHNzc3JycnFxcXBwcG9vb25ubm1tbWxsbGtra2pqamlpaWhoaGdnZ2ZmZmVlZWRkZGNjY2JiYmFhYWBgYF9fX15eXl1dXVxcXFtbW1paWllZWVhYWFdXV1ZWVlVVVVRUVFNTU1JSUlFRUVBQUE9PT05OTk1NTUxMTEtLS0pKSklJSUhISEdHR0ZGRkVFRURERENDQ0JCQkFBQUBAQD8/Pz4+Pj09PTw8PDs7Ozo6Ojk5OTg4ODc3NzY2NjU1NTQ0NDMzMzIyMjExMTAwMC8vLy4uLi0tLSwsLCsrKyoqKikpKSgoKCcnJyYmJiUlJSQkJCMjIyIiIiEhISAgIB8fHx4eHh0dHRwcHBsbGxoaGhkZGRgYGBcXFxYWFhUVFRQUFBMTExISEhERERAQEA8PDw4ODg0NDQwMDAsLCwoKCgkJCQgICAcHBwYGBgUFBQQEBAMDAwICAgEBAQAAACH5BAAHAP8ALAAAAABxAFUAAAj/AKuwcCDhAogrWUhEmBChQxEjME6UMNGDyAgLFyQ84PGjQgMGDSzkiOGgwIABGo6oUEAAQcgYIRAIAECAgg0XHSxAYHBAxhMbHiIwADnBgoMDAQgoSLGDBIgdYJaM2BEFiAiNFSzsIFPFRokNGjycMAKFCMccKUboUNPGSpAoSERAwODBhREeHxw0uMCixw0WKkygqCGjhIadHl644NAgQQQQKC4ueLAggwoWHBgQaOBhhIgMDAbUJKFCRIcNFjSo6CHEa4UFFSY0yHAVQQQOOIzsgCHCQ4gTMHbISMFiBY0fPIgo4cFixAcSMYA4UeKjBosUOYxwCIGEC48MSCPI/xiiQ4SCAhZW6NjBIgQIFoRVbDhw4IMNGs0NKDihY8WEBAAE4EEMLHTQAAACaOACDCZMAAAAEHyQwgonWNBSCD9MUcUQNeBAxBE9yOCCBw48QJsLOvwgwwcXdGACDT3oEEMJGHQgAxFNLFEEDSaQUEILPzAxhRIyqoABBCokMUURHojWwQ5M+DDCAgZQoMIPQYz4Gw08xGAhAhvgEMQOIzjAgQkgiADCAw9WgAINOGjwIAQl4JfBTAM0hAIJAAowAQxPWHGEDA4A4EAIKsiggwsLAEABCjkM8cOMIyA6ww03LFYBBynwwIQTTgQhAwqDHRGFFDzoUEIFqjlBhhEfNP8ggQcz6KjBAQxYcMIPQ8BQqQoz/KDDBgUg8CgNKlTgQAYbPHeCBgME0MAHC4pQAAAI8IWDCmwCUMAHTBxxAgUKUAACCCOgAAIDARwAQQeJrnABAghUQAINQRChAws+ovDCDTVE5AEIKdAwRBNOFKGDDDPk0AELQ/TQQgkfxNDEqxcwAMEGNRCRwwfn/XkEEjqE4AAGnfZQAgMFLACCCydwgMEEszoVwYMDPPDiCYUWAAEJNuTgQgknBNHCmzPM0AEDDmhgXAwnYKDAARK8jIMMHEjQQAQD/qDwDC2wAAMOQACBA4MkmABDD0pEocQQJYAQQg1D+LACCB7IgEUXO0T/MMACGLTggw0cIGBAAyMsoQQJCHjrQRFMyHCBAu+isMIGF2DAAAInzOBCChMMYIAEJLBgwUkCXABDwBtsfYEJLLigAggXOMB1CzcMl0EDClxAAw0wvOABBRFQQAIOQjCxhA80vBDDDmX7cPYKKrSQAxElhMBBajD4EENaFsTQhRcvRNDYA/fGwIECSa0wxRMuHCgADk5IkQNoBeiMwqxR6FFHJYVowxmgcIICMGABB6iABA4AAAOooAhNsYAEIFABEKwABi34QAQU8ABaCaEHKLiAAQzggBHQIAc7eIFOLsCfJVQBCkIgDA1soAMeBAEIPeCBD36QgxN0AAO0mQEQ/2pQgglsoAdZ+MIPLvAgBRwPBy74QAIkQINPzaBQAPhAEa5QBBEwcABDmEMk/gAJTmSiFbbghB6W0K0EgOAEFwDQAi4gghWsoAQSQMBcgEUgEERgWSzgQWtmN5kNnOAGPajBCjhQAQyQIAdMoEIVpECkGMDABkNQgkpogIQflEAE8OFBDoBAgwxAwANB8EIWYhAa26QACUhQAQQW8KgjQKEF1wJABnDQhSxoUA104MITnDAEJlhBDYsgxS40sYMHAYABGhiBBxoFgARCZ0Raq0AIWtAhGHxAAhX4QAuOsAUhqIADEGjABl6ggx7koAUg0EAGTNiENOABDlXoQQxooP8DEoTgkVdQQgtMMJET9GAILciAB1aARCqwQAEGOEADSOADHdwqAQ4oQRHgx0AATMAGTpjCFZZAhCU8YQtbyMIUpCCIYHAjGFTAgAEedID0tOACM3nmB2ZYGgxIIAMlgEGKXHCYCHzACFRAghBoMDwNDGYHQaiOCTZAgQ/gAAtyyMMapkAEEYQgBB+AwYdoQJoQjOAGTEiCDEbQgRMAoQlDEEECBoCt473gAg9owARSoIQdUOBAAwDCFpaQhTbIARCHuIMfAlGHMqiBD78wxhhokAIKEIAmFGABjyzAOwVk4AQ2EFUHKGABELxgBz7IAQo6IAIRUCt5SMCBCT4wAhb/3MAHRTiCD2AQAg2UwAZNGMMamDAD34jgRUoQwgpSoIISlEAIWlDJwB65hB5sIAEEEAADRtCwHHjgAAqgWQckMIIrVEEMdIjDF8KghjjIwQ5wyMIa/GAHWVziCjmogfAQEIAI1LEGNhiBAgRggAV8QAd1s4EJJFgXqOIgBR/YAAc+MIMjIIQHKSCBCFJQgyI0gTqF2dIPAnqCDTjXAzpYDszQJAIibKEJCe0A0IzwgxNQswAmIIMYcHDjCAThC2zwQhniYIY1zEEPcojDHNwWCkjUQRWUuIIJVECDGXCAAzFoFAdm4AOsAQgAEVhBEGjcghBYgAIiwFcQFnaCeJ7g/whJ+JQPBpquGgRhZEbowQxAoIIobMGiIXiBCFxQBFJOBAULtcIboKC9z4RgBjb4wExp8oIxiEEGdKUAHdzQBDmoohB36EMe1AsFJtQgBF0IBB5OgYlAUIGsG8aBFmDQqAKIAAc+6AEOSPAgA3wASkz4AQkeoAAKQ5UIIQKBAyqwKylYYQkqci4LdJCEKFAhYCV4wTBR8AHTpAAHAD6BCaKYgbWUwUsWWIAA3rwDDQggAABIgAzE0AUSWAASX6hDM1AhCEQYgg9uEIMWnhDDFkThEKnARB2+YAQbEMcPuZgFFSqQAQMMoAM5gIIWkICCyxagAibYwRKYgJcJaOAEz/8zQnXh+QETxKAHRpgCFZZqghPUwG6CUSgQpqC0H5ZACQ+hwUQuYgEeZMEIq80ABhwpBB5I7bIMoMETpPCHPRgjF4yIRCUa8Qc59CERbdBCFYpggzB04hSG+MIUmKCFUaTjHv94BQVclAEJRMA2MDjCEWyQgQQUWAMVbgIRYOCBDoygTkBIwhJElQLL1UAIS4gCV2kQAzUgQXthYYEQivDgESCBBxxIMw1Mw4Fo7mAKRmDBBjDwRxPcxQONCwACcNAHVASjEpG4BCUccYhEtEITahADF6pwlzykwhN6OAMW2pAJYnyDHf6IhRNI4IEPUKBxAFAAB3oQhSLIoAItecD/B2qwBCoQIQYe2ACicoAEKXQ/B2B7QQ1+kIQnQIEFXoCDDhTa2hZswRBNcAEaIAIS9AJEMAOYIzMbEARa8AMd0CIOoAAzEAU9MAEzEQKGQAqg0AiWQAmRYAmYcAzSwAhqwAZj0EtJ4Aa6YAhxAAZ7oENu4AWBYAraIA+/cAdbAAa7JkLZpQAoYEyeVAEQ8AAP0GdgwAVL1Rwm0AI70ARbgAVKcEM8gEhFEGFOIAYcVwElQH1L8AdkgEf+JAEfMARJwALh5AEJIAJK4ARW1gF38gBAQAU6sAAwYAl+gAiP4IGRwAm4YA7BgAh3UAdywAZhoAWC0AynoAVfQAqe0Aut/+AJrcAHM9AEunAOonAHRVAC4GYCFMBAAZA4KAUEIaA1lhEETZAFWdAEQgADJaACPmAFbjAHd6AHXSAEOXBBHDACQGAFQtABcvMnZyAHRMABA6MXLPAER0ACLXIBFNACnxJCHFAoFjAERmAFkQAIe9AIlwAJklAJszAMo6AIhuAHfaAHbtAGpOAMxmAIyHAO7xAP6pAP7tANqYAEBKACZIAHZhACD5ACNuBOJhABLZMBMYAEVNAEMCAbFMABYjYFWjAkpLQDVZAFVdAGfRCDIQA7KHACMhAFViADJDAhKxAGnOAGLcABtOMAHuADV/ADIaAm8gQDZWECp6FuIHAHhP9gCafgCI4QCZiwdZCwCaUACpjACIZwCIgQDMwQCsKgDtUwDdpQDuXwDeGgDueQCSOQAWdwCXDQdxZQMWumAhcQARGghT2ABVjABEWwAhrQcjAABE/wBVuQBLlTZTwgBWAwB0CgASbwAuqiA11gBCHgASigAjGQBGkQBRehARdwSkywBTlwAaxSARdwA0lgXRfgAjwwBzpwiK4wCJMQC9kACmhgCKYACrJQCpSgCaFADc8wCtJwDdfQDd4gDudgDuLgDd3QDtbwBRXwBIiwBRbAEBKwAW9yNkaAAxaAAQx5BF1QTivQAc8xAi5ABFzgBVFABDbQAv8oBHIABs6hAn7/WQJIEAUlQEEXYAEkIAVnAHoYUAGUoQJawAUosAAQQAECuANIgClrgAUOMAbEYAuHsAi/0A6ogAehAAqjQAymoAmeYAvgoA3NgA3gYA7qwA7pgA7soA7kIA7aMA7nQAgeYARx8AQ3YCQWkAFtVQNG4AMu8BmZ4wE04ARdUAVA0AKH5y/cVwZlQAWFVjVqoAc2oJ4vkAIYcANb8B1hoQE2YQZZwAIaEAJ9twA8MAZGUHcWAJ8cQARiUAYpwABP4AunAAeIcAidIAmdUAqnAAyyEAqXwAnMAA7JgA3ngA7r4A7wkKft8A4Yag7eEA71kAkxwATDZQRSQCYS5gFBxQNm/1MaP+RUQZAFwvROywUfTfAFXjBrIDABR5AHVyCZzSOjW3AFKrAsITAXR/AFR2ACGLABEBAAGuAEZXAEE5BAtAEFU4AELjAEpmAHZbAJk4QIn1AKsHAMtYAKj4ALwkAJxXCn7iAP9DAP9FAP8hAP8OAO7cAO5XAO+TAJNWAFU+ABfPUERfACHaABxIgCOzAEQYAD8KQBjkQDSfAFUaEDOEoCgpMEMNAcKDMGdBAnNDA0JHCWRJAR2kMB3MEFOOBIJsMAKjAGZ/ACEJABgZYB7lMGmgAGQuAIgPADbkAIkPAJnlAKqfAKxmAHanAN9eAO7hAP0koP9IAP9RAP8iAP7v/ADuigDu0gCERwBjhwABjAAkawBVVQAxtQARsAAiUwA5BXBDUwWyBAAnSzBCjVBDzSAp5TcyvwARgABYcgBSNwAixAkySgBFfwAgtJgA+wAlcABSnALEfrADdwBlLwAZT5ABYgAlZQCG4QKHxgBm6ABTYgCXPwEHBAiIhADfMAD/iQD/hgD/nAD/6gD/cwrfIAD/GwDjgrB0yABWuCOSEQBFvQgCSgUIf3Aj6gBDAUESUwAiMgZl5gBlsQBDUABj8AAjU3IiiQBmlwAyGAAi4wAhyApFbAAUbkqhkgBFegAx8Arx7BqW6gBB4gAaZRA2FgBGGgsWQwBlAwAnrwBjf/UAIdUAV/IAnKYA/tQA29MAuuEAu+wA3+4A/48Lj0QLPrcA/YsHhJIAJFwYwdgFZpKQOD6borwAOnGAW7lQImcHgzkARYEAY4AAZToAK/EQPSVARswASH11z/hARZgLabghol0ATXRlUZkAEXMAJeoAY6MAIMAAJZUAIrEAVM8AZj8I9d0AYc4ANdEAeMwAnNoA6uwAiIgAiE0AdxkAar0A/y+7jzMA/xwA78UAriQwUxsAIekAHaxANFAAWFuAMnMAIkQALAMQRcwAVOwANgowIpEBxf2QVoUANvhAKfQQJiQAYwIAG/KwIUYAKUVES0oQEVAANSwAQqkBplAgAs/zAGX9ACE0AEJtAbIIAGbrADOFAEVGBecSAIlzAKyFALhOAIk1AJk8AIgjAGVoAN/5AP+pAP9mAP9OAO9zAOVoAFlcy0yLICMgADpXOF1zsDKqABEzACPTAFXBEu1fECK8ACdhsEZjAEF8ABUksiRiAHSTABHUAqp/EDcvgAxTkzFuADWSAEGEABFQIADWAEc6AFQUABJeIAOcARPbAEUwAGb6AHkOAJo4ALonAIIHgJlyAJiYAHVUAL/8APjju/9nC5++AIPqAHUXAoN3AERTADJMAB2WaZT0AGZaAFYKADN5AD/7KGSMAESOADJ6AC9rbIVzACFqQCHoABMMAGX//Ily9gAk6jBWQwAhHyAXV3Ak9ABS0QTUFRABuAVUMwUxVwUxsABcD0BXDQCJwgCp4gCprgCJbACZuACZbgCH6ABY/AxPow1q5MD+zQD9QQBGMACCswsR7QAsqBYSbQeKflBHeACpDgUCsQAy2wXDXQBPW2kc1VAlAwBjrAWiNgAh5wAUqgBkDwAR/wJj4ABICrBCGAtKvXATxABUFwLiCwASxBAl+QBjAAAQcQAlXwBniAB2sACJjACZ3ACW8QB5VgCZqg1ZgACYQQBnLgDv+wD/vAD5RbD/BAD/0wB0NACVmwAR0wARXgAVeCBUgwA8uFAiZwA1LQBoMQCGkABHb/dB0okgOld2IboANtQAUckAESwoksYAZwUAN8cQOEhwJQwAUygLcekN4hMARF+wEd4AGcFQE1YHkecAV5sAd6gAd7sAiX0AmagAUZQAeh4JObwAmXEAmHoAZdoA2/zQ/8sA/D/Q7/QAs3kAiEcAIicGUqwANXMAZJoHc30ALiiSlJoAV3gAhwkAQz8C87QAhbIAESsCdsBQJeIAYoUEHXkRFbAAlLkLcnkH4OIJ9J0DqtmgG6AgX3pwHosgEDwABQYAZ2MAiEYAiKQAmZAAqK8AY6IAF3wAujvAmbkAmTkAh1AAWt8A/+0A/BvQ/3sND74A5WUAagsFJUkARBwCEl/4ACTBsFT0AE1jEDQcADLSAEa5AIgXAFOBAse8AHCpHoWzsBSsAGQ1ABHaDMGAAAKPAHbXA3kUwCPwUFXwADDEDqHUCWOnAFRtBaH+ABP1AGg5AIPVkJlTAKvVogUCAGfEAMp8AIt23hjsAHUeAH+HDQ/NAPwp0P9wAP/1AJQAAKeuADdfAHTcC/72FHj7cE07EDWCvpJ4AEacAHfuAGKNACb9AIP/AAIqACKyAClVkGXUA7LWCkBAABVJAH4jI7I3C0MmAFU76QAO5fSZAFAc8Fg/AIH4gJZ2cKgDAEDbAAFmABLlAGv3AMh4AJm+AJmjAJgqAFXVAO/2DtHg7i+P8QD/7ADECwB6CQej1ABWYguKfb1y4AJVnQBUgAJzyAH9/qBsyznoyABRqQzSrAViIwBXEwAxDwTyDwVzIQB17QAiTwAb+YAT7wBC+QARzghkJIAk3gBo7w2qKACqjgCYFIBxzQQI1UAC3QCuYQCY/QCZ2wCZZgCGfABMKA5/7g4QidD/WAD+8ABk3gCXSQAsxVA1FABlNAVigAGCvATlOgBmQAbTsgSjMAIzuQKTzgB35gAg+gtJFRATPABk/AnK1oAhCgAVRAB0eANx/wVW2VBE1RIROQATdAB5nACaSACqowCWWwAxOwAEogCF2wAQJAEArwBvXACoKg1ZpgCYr/MAdKQAr/cNDWTtb4MA/70Ag6EAiT0AMfEBiJLQRiQAXvJLYwQHk3UAVz4AZaUAS5FgQ3gAMAcaOGihRsEumA0AGFCRAaRFzxYkJCiRUkNjSQcYfMChEkVIz48APJjh0xcFAJpGmUrFSf7NSQEGHDBQEezgBa8qABAg3LtPWxtCnTpUZ6oETq54/fUn769OWr508WETCVupT4QMJFCxQxhlzRAqSFChg4aOxA0uQMnjVLeqh4sUPFhxUdpjy6ogHDCRYtRFgwsuZHhQwhQHA4UAHMnSIoPJiw0iXRIjdhwPjBREqYtFufAL2BA0aJjxcVBsDAQ+fHBwNS2mFKxElT/6ZIg7LwyfcvaT9++/Lhu/cPmhYnf+o4USKjhIoYMFjMOJKlC5IZLcri4PrjTJ86bnyw8DFCxIwSQPS8aYFBBQsWJSjEWLOlA4QRIUZYSNDDy4oaZRpFUoaTVyxJAxJQeOmFkkDsUCOLNNw4o4wqlEjCiCm8cGMIxFRZRhBNONmEkkTGWIOdf/zZrbd87vHnnDaYSEMQJ5DoQgoYUhBCiSFsoOGIMMzIwgcXZtjBBoKICIOPQYQ4gYYRNpBhBhbEEASIC5ZD4QQORohiDRooGEFLCiBIAQtAFCmFm1escOQQQk7ZpRdG2uCiCi+64EIMMM74www13NBjjRpoMKEAHv+SsUSSTjaxhBE2vOBGt914e8qfewhhQotAwigCByW6eEKLKoQIwokhglDiizi8AGKGG3ig4QUXagAjESxkqOEEEHLgwYUp+rCCAw5KqAEHLYOI4wkOOigBhyPGMIQSTV6hhhhAsgDkFVlkAWUQO9wQY4kt+FiDDDseIUQONzBJxYokkLDBA0NYmWSTTjBhhA4rjtHNH395a0off0Z5Igo74PCiiBJsKCILNq4IggcdeJTBhyzUAMMIG3L4oYchdogCEjdoqGGHIZAI4oUiAIGDBApMcOEFFEaggQwpcJiCCi8UGWUWXoIpxhpkHDnFFlZA2SQRPdRIY44z8mijjj3//ihkkUxouaUPNM4gYwoyPBFqE0we6eOKVf45cdKm8Pnnly2gOGMOVbOQYT0h5sDDiK2GrCGGHqyIMIoggABCCBx2uKOOHGIIooggfMgBhzT+EIKwEl5YAYUPnBjkjTxK6QUZY4xJ5phmlgkGGmywCYYVUvr4QQk/KvHjDT348OOPQBIpJRdRGgEkcToW6aT4sQHR4hK0d/tHxdyycYOKLebAUBA0epjBBRaacKMNKW6AYQYdciiWiTDKyAKHHpTAAYY7EEECBY5/GEKHFKK45AwKNLhhBhVISEMqUCGMZSgjGcEwhuiU4QxraGMb1yBGIdJQBC2wgQqCQITt/IAH/z3IYQxjiEMd8uCHPejBD4rwRClE0QlJDKIqaPMXipLSlH+wow9ZuEIbyICGDvYABjzIwQtmQIU82MELQ3BV4SZ2hTlEAQk9AEL9xCAJLaigZDr4wQ9WkIRPCAIDD2DPDMpwCFLgwhe94AUvZoGKUXDCEpOIBCMasYgicAAHbZiEJRbRh9zlTmtd8IIXxsCGOOSBEI+4hChOMQpPUKIQYvgEDPuhm6X4xh/0YMQXsnCGMZjBDm9gggtk8AMg/AAHPKgCHQChh+kgIQlKiMIVkGCEHOjACEBIARMcUQYZ3OAGOQjCEGCgA0ZUYgYYiIEPwjAIQPiBEIsICip2AYxbvP/iFJwYRSu0MIK/aQEOmmDFKSgxiNzFYQxvqIMdONiHQUjiE6MoBSlIAQpLGAINwZBUDPtBqX34QxVg0IIYuMaGLLThDUngwRGWoAQj9KAHF8xEHHRwgyM0AQpEuIEQfsArHrRgCISggw5koL4eBGEGL4BDJbAQA6qgSRWnAAUtquELW/iCF7nwxTBaAQYToFIMbrjDICyhClZ4whGAmMMeFgEJSDwCEpO4xCdMgYpUnEKFnkAEIdSRT950dR/6+Ac2GqQFMpAhDUTggRXcgAYoKEEJSHhrD3hwGztUgQdo4QQYXuCDHtjglL+Mwx6EEIMc7ABVRbhBExoRiCH0YAr/aIgDHiT7iVxIIg97kMQm2OCCFCRhC2WYwx8SAYlMpAIXtEiFJy6hiVCIYhSkKAUqVuGKV8TiFbRtRSs2YYt1gDWGKKqkwOzRCCxg4Qt6YsJocuCEHVJhCEWA6xJeOQU4/CEMSSiDKN4gPyDg4AY9yAELxnCIKLggCVVowhGKQD5DaEIJJfABLLHQBSjMIRSIEAQeguCBuY4BUINIRCQ0YQpZFAMauEiFKgSIClSoYhWtkAUtamGLWtQiFrOYBS6ekQ1stANtkuyNU/6RjCxIQXpheIITngCFH+wADdzJQhEq2oQnJCEIV8iDIGYQBVX0gQU6IAIQcsDXE2BBE2vY/4EQerCDHHRKBXq4RRk+wNIjRKEKTOjCJCZhgg40IQxryLEh/oOJUKRCFsuIxjOEsYrWioIUplCwKl76iVYEoxWqaEUucNGLZmDDG91YB9s+/OFnYKFgXhADEIywhbIqIQdIaIMc0nAFIhihCVOYwhCIkIUo7EAJoVAEDnLAUB74YAgxgAIqJFGDFvi1BkBcwRleAYitDOEITIjCEnbwAhtoQQ1z8IMhFOGISWRCqqnQBS7wwIhaDMMVofiEJz4hClG01hOVWAUvasGKVcRCFrY4RjRq0YtpSgMc6UBHOd4RjlB4QQlf1oIdriCEJTDBCU0wgg+QcIY9xGEKSIiCFf+u4IQkNCEKpGQqEHZQhB74YAlDYEEOOIGJwf4SBxOlARIAIQknwKBwQaCBCoJABjnogRCIWGolNNEJUZiiFcrYxBe+MIZEzOIVofCEJ4rXCU/MJha6oKYrYlELWcBCGMdIBi5KMdVTmOIVrPjEJODAhDHo4Ql5gAQagBCEJzxhCsg5ghPeQAg4ZKEJTZCCFYiABCYEoQeF8EQTvNLdwroABhtPggoKywMgYFQFWSjEFVywghPcQApmqIMzh02JTHCiE6EghSqAIQtRMAINY9hCG04BC1B8ohOcAFElLkELXgADGLjQlixKQQtwNAMZvkCFKV47C3oiYg5c4IIfuCD/hC1QghJrQGgSoBAFKTjBQnAQxBuuDAUjHAEJs7yBHlLxBPnNIAY1uAENWmAGRzRhBj74gRCGAAQdrGAIf8iBBorwhTfwgRCHYAQkLPGhaY9iFcTAxRvwoNszdCELb1gFWMA5ENGESsiEWciFXxgGX5iFVmAFU4AFaLgFVliGZGiFVHAFWxiFTVgEOwiDLzAELyC4IigDQMgEO1AvXEs7KVACLPgDRXADLDgCUrE1IFgBOkAFJiABG6gBXtsBQpmCQJgCGyBCHwgyHjABEuCBKUiDOxAERXiESKAETOAEUAAFRYKFYmAFM9iTNxAFVdADL9iCOmCFWiiFUOiETAiK/wwDBmHwhVx4BVJAhVZwhmpQhUqIBmaIBVzQhTdiBD8olzjogiMYgiRQMjM4hUxQgxl8AizIAi1wIi3gA0Y4AiKgHyIYgh+QgTPYhCsglv6pgSg5ASBwAybQAR8wAiLQARWQASDogjnoA5RzBEpYrWmjtlXYBVl4BDCwAjN4AzU4g0SAhU04Ayygg1XAhUXyBEzYBFiwBWEIBl7YBQQzBVvohWN4hkmQhGUAhmkCBUqYhHQBgzHQgh6InSdwNChwA0ggRlu7gi3ggizoOi5wJSUogiEQPx+AgTLwhCywohgQIh64qy2AAzsAAhlYgRhggi8AFENwBEiQQk7whNYqhf9X6AVdiIVQSC42OAM/AUY7MAVdaIQ0IMNcWAVR8AROcIVbAIY3RKNbaIVdCAZZOAZhAIRHYAVasDlM0AREoIMwCINipB8jKIKzUwIngANIQIQvEIIoACQuuIIokAQuKAIba0oxgAMrMII/UAMVcIEbULQj8EVMwIMK+AAkQIM96ANCYIQ8mr/HI4Uy5AVb0IQ0cAJDGAW2EgM0IIMzeIM2SANMSIZVCCpUsAVXUCEzswU+1IVd8AVdeIVduKlkSAVA0ASk4YRPwARJ6IM0oAI9mAVDkAIikDEmoAIncoI6GAVHCAMnqAJIvAJH4IQsGIIvwwMwiAIggIHzGxmB0IH/MECE1NAABxgDQTCEQjCER6gEZvyEzbsFWjAFSrgDu0LFJdgDVwgEKvgCNeBOOagDOSAEXcgFRviDTcgFW3gpbxs6rNEFXYAFXPiFW/CFX7AEbLKE/3iERfCDNDgDOTCDULgFP1gCITACJqiCLbiCJtACRohOMzACJbACHjiDSQiENOCCKFCCrRuCITMEO/ihK+ADToCEMhCyMpAERzAESLhMVIiFxbwvVMCDFCgBHUgCe4MCGkFJMtCCNrgD79SDH/2EXeAEQaAEBCy6WIgFWqCwXNgFW5iFi7yFYIAFUliFUciER1AEQugDM+CDR0gDLeiDcGoDIOiBJHiCKsiC/ylYAiyYBFw4BCtYgiJ4AS4QhSy4ASSAgic4giDIga/kAiv4KT6ohFFYhDeIgiEwA+JRBVloBWtSBDMoAhd4Ak74gxaQgShwAtfUgldyBFjggyr4NTlQJUG4A0joBVlYBEo4BmLIBduaBVtYTPG8hT3jQ17gNlMIhUxwhETYgzb4hExIviuAAkMQBlUQg1NagimoAkMrAi+oT5xAghUIA1K4gh0YPiQQAieYAiFAyzuIFlCQhVHggzPwAikoA03QhWfghCU4gsWJrxo4gk+whBpYAa+DAikosSPIgz6EAjCogzqYAz0wBEE4BMmzhVNghWmqhQyr1Ti5KV2whV/oBf9fUIVEYARKcJRDoAVKyK86UAMl4AJUiIZMgBfTjAItyALpQoNEOBkjgAEz+AQsAAIpOAIsQJgvGARMcAVfCIZhOIZjOAVDWBA80ARaYIZIIIEaIIJ6WwIn+IEisIRSmIIeYAIpsDItkKUweIVWcAM1+Fc66INDEFpOqIVP2IRTuIVfmMZdYFtpTCNwQwZeqIZmgIVLiARBsIRqEAVBKAQ+gIM3UL4jkANd8IU7aL4pgIIpsAIqSFyiVAIieAExiDmccZo3sARY+AVi+AWgS4ZniIbJk7AnvYZY+AEYaFrXzIIt+LpCoAU9IAIDRdM7gYIrqIRU+IMyaL898INDWAT/Q5CEVrCFpcuFYdDcNPoFBUSGZEAGYdCzZBCGWUgFU/gFdPgGVNADOpADNWiDNhADKLCCOhCFV6gDdr1XZdUCJWCCIiglGMACT2jNKIQE0zojVzitXOgzbNgGbrAGaMiGc8iFQcCCHZCCMUADOkAYLlBHWHiEJkAC6dkCMEhTM7CEonIDNwiEQqiaSpgESliFVrUFZIiGaXCGYhAGZmCGZDAGYcCFWoCFVhCFTXAFZ8CFTMCFckgGQjADNFiDMxADLoyCKACEXnCFNHhQKjBihToCUxuC+ykFM2CDWBheZGhDEjaGzM0GcgCHbwiHcugGY4geVVAEf40DMzCDLvis/yqwgtjqAiOggtdEAwa1hDuoqT14gwBjhMwiBVFwBWI4hmFIBmsIB3HwBm9ohjfEBVyQhVeAhWCwBm4QBlADNmIQh1OAgzK4vC8ggzGgyiswBWjgBC04gjNtvubTUBZAA1fQoUhQhV4oBj7GBV74hVeohVyQBZsjBUWAgzyIgjEQhk6Ygiz4AiwAg5l7Ay2wAk+oBTpJBD7Yg0PwTzWAA1dghkmYg0eYOE4ohViAMGRwhgKihm84B3YYh2t4hmVAOnFIB2/QhmV4haThgzKmBGpQBkLQAjAggzDwgjIwgyYYAjn4YEeIAtJsviXA1iRwgiDwg2MABT7Qgz0IhEMQBP9AAIRA8INA+IM4+EuBqgRdEAQoUANaEIUquIIyKAM2YIM/SQMwQARgeAU6foM0aAM6wIPv5ARoiIVEoIRQGAVQYNHa8oVogAZjWIZq2AZ0RodwAAdxCId4OIdfiAVTsARIIAQ8+FI2CAVpcAU16AJOEgPL0IJk5QNPcAVFmAIjSIIkYFckkIIpYAJAuIZjSIUqdbOlS4XoZYVYIIUy8wVmKAU/sAMyKANY2AU2GAM5iBo9uIM7MKdA0IVMMGk74IM8yINACAQ9kAQUJgo3G4UE0xZbaIZoKIZhGIZlcIZncIZkWIZhoAVk6IZceIRBOLlAoAM1+IIrkINZUIZJIIP/HiaDL+ACL9CCFGOEY3iFMLgBI4CrI1ACNH6CORAGV0CETkBY2+K2paMGcKCFWHAFTTCEPyiiNBgDVCgGQziDSMuDP9iDO9ADNzjGVriDNwgEQdAdDCYERaiFZ0gFR/gENlIkbYmFXYgGZ+CFW0AQX9iFCnsFTjCFZfAFRKgDEyKEpSmDLsACQhgGXOCDLAADMwgDLgADMdgCLBiDSgiFKciBV6oQKKiCKnCCNhgGSbgM2D4EsUWEiXYFWQgEh/ZrOcAbOSgDTmCFQoCDv4XFPxiEQrgDOBCFXiiENfADRCgEQjAEQojyUbCGYAgKUhgF1zoFVCCFM7sGYFgFPGMF/1QoBUxIFEiQBWTohDugAzrYAzq4Z9z8gk8oBkwAgy4og3sOgzHA5xSAA1KgAqpF66Ns4yFQg2GYBDCgAz2QLDuQgzgoAzFIJDtggx7d3jfoVS5Yg6X6JDmwA4KlaDrWhF+IBDawA9guhEEwBEYQKtQehUrwBFTwvFNIBdj6hWkQhlKwhEsQG0ggWEXAA0fYBVhAnjDoHjHogi+oAiM4A1rwhUPwgi84A64B7BlYgUsoBiZYH+VWgpyRAiNAA11ghIJSFzcwAz0RlUUYBTj4AjNwAzuYgzRAAzP47UDYhDxYg3TCA/UGBD5Qg0LIhVIAXD/wA0AYBJQjBEJ4BWiYhf9HyATVwgRPeK1OGAVhcAZUGARFgARGUARE+ANAqANA4IRc6IT+U4M1SAMx8AIuMM1GCIZYwAMP35owIIJX04VZqERMTS4Vr4IgwAJXEITDUwMu7IKjz4IqIARPSAM6GIQ46Fo1QAMwEEM72IQ+KIM1QKc7GKGlsYOikwM0uIM92APc4dvyXIZdQARGwARKeATGuwRIcARUSIZZC9so/wM9yAM6MIM7kAVX0IMxSIM1qPcPzALmCoVi+IQwuIKycqUa0IFXGAW4uje3Gr5M24JS4AMnOB8veMQQx4IoyANaYIQ9d2I06HMyAAPb7gQ+EIM2mIOv5fo1WANRiIU9MIP/OLADEdLdQfiDRfCFYpiEQmgqRYBCR1iE3emFX5AEPNgDPMiDH7V0MAADRoAFR8hkrhmDPPeC06QDVeAFRACktK6BIEhw0lwCo3wCKagCIvCCUggEKejzeHREPJGCPKAENRgDNSiDMcjkDwSINWfUKPJjJs0bOHHo3KHjBs2jWorStLGTB0+ePoMICUJlzFShQosUHUKkiFCeQJ16sdLTJk4cOXTqyDkDBk4nUHC2hAGDxoyXLVqkTPFj61QfJ0uSJMlhZFWlIEKaNFGSRApRI1hUIWKyBQwXLFu6BG0Sx5OeMmfKlAmzZQ2hN13AJEI0ZoybNwnnxIFjZg8tUm3K/9jBc2fPHkCB9CzaVcsQnkCD+Ozh86dPHEK7dBUig8aNmzl15qjpksWQq0Zksohh88ZMl7BKqnDaBeWJk6o2jrBqxGOIkuBKljSJEqQLLUJKuHwRuoXL2DeAMunpgmYMGDFw7ozZYkULo0ZfuKhRs8aNHDtyxsB5VYvOFzp5+M65o8dNHFXDHJl5s+dOHnvkkYcba3yiSyRhjAFTG3C4oQYYUJQBCit5YAGGGg9xkQUXVlAhhhJNOCEFE0jU8EQsjQBhRIhHFBFFGGQkgUYvfyBhRRdbXKEFWWx0sYYneVxBxhdmsGFGGWR4QQUWhFBSxhZmHBQaHXOQEYYovwQCxv8bc7ihUBx3vIEGJsV8ggYbF1V5Bx90lPEGLKqs0ZqDcrhhRhhUTFGILZCAEUUWZhCZxRZbTJFEiE4otUQOVswyyBGIUvGFgmNgUUQaq9zRxBZeXPFdFjxGMQYmeHQRRhhmiBFjGF9YEYUflbThxRhlmKFGG1+mIUYjtCQCxhp9vcHGS22Q8UctqOTRpXptyFGHm2tYYsofZ6TBBhtrsGHTFk6M8Ugld3RR5BlucQFGE0ccwQQTIibxgh3AnJHEFm+oYUUVXoCBBRB1wIIGFGRVMcUVzHWhhBieyBGGGF54oSAZRFYhRR6VnMGcGIL2J8cbWxDyCyBZpOHgGmmc4VopF2PQQgsdasjXxmdv3LHGabAI8gUYZKRRnpJdTMFEIL4QssUYEY+hb0AAOw==');
111 break;
112 case 'tableheader-bg-grey.gif':
113 sendImage('R0lGODlhqgsyAKIAAPHx8e/v7+3t7ezs7Orq6unp6ejo6Ofn5yH5BAAHAP8ALAAAAACqCzIAAAP/CLrc/jDKSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodEolBq7YrHbL7Xq/4LB4TC6bz+i0es1uu9/wuHxOr9vv+Lx+z+/7/4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGRAqSlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7y9vr/AwcLDxMXGx8jJysvMzc7P0NHS09TV1tfY2drb3N3e3+Dh4uPk5ebn6Onq6+zt7t4D8fLz9PX29/j5+vv8/f7/AAMKHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mix/6PHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6bNmzhz6ty5koDPn0CDCh1KtKjRo0iTKl3KtKnTp1CjSp1KtarVq1izat3KtavXr2DDih1LtqzZs2jTql3Ltq3bt3Djyp1Lt67du3jz6t3Lt6/fv4ADCx5MuLDhw4jxFljMuLHjx5AjS55MubLly5gza97MubPnz6BDix5NurTp06hTq17NurXr17Bjy55Nu7bt27hz697Nu7fv38CDCx9OvLjx48iTK1/OvLnz59CjS59OvXpxA9iza9/Ovbv37+DDix9Pvrz58+jTq1/Pvr379/Djy59Pv779+/jz69/Pv7////8ABijggAQWaOCBCCao4IIMNujggxBGKOGEFFZo4YUYZqjhhhx26OGHIIYo4ogklmjiiSimqOKKLLbo4oswxijjjDTWaOONOOao44489ujjj0AGKSSLBxRp5JFIJqnkkkw26eSTUEYp5ZRUVmnllVhmqeWWXHbp5ZdghinmmGSWaeaZaKap5ppstunmm3DGKeecdNZp55145qnnnnz26eefgAYq6KCEFmrooYgmquiijDbq6KOQRirppJRWaumlmGaq6aacdurpp6CGKuqopJZq6qmopqrqqqy26uqrsMYq66y01mrrrbjmquuuvPbq66/ABivssMQWa+yxyCar7LL/zDbr7LPQRivttNRWa+212Gar7bbcduvtt+CGK+645JZr7rnopqvuuuy26+678MYr77z01mvvvfjmq+++/Pbr778AByzwwAQXbPDBCCes8MIMN+zwwxBHLPHEFFds8cUYZ6zxxhx37PHHIIcs8sgkl2zyySinrPLKLLfs8sswxyzzzDTXbPPNOOes88489+zzz0AHLfTQRBdt9NFIJ6300kw37fTTUEct9dRUV2311VhnrfXWXHft9ddghy322GSXbfbZaKet9tpst+3223DHLffcdNdt991456333nz37fffgAcu+OCEF2744YgnrvjijDfu+OOQRy755JRXbvnl0phnrvnmnHfu+eeghy766KSXbvrpqKeu+uqst+7667DHLvvstNdu++2456777rz37vvvwAcv/PDEF2/88cgnr/zyzDfv/PPQRy/99NRXb/312Gev/fbcd+/99+CHL/745Jdv/vnop6/++uy37/778Mcv//z012///fjnr//+/Pfv//8ADKAAB0jAAhrwgAhMoAIXyMAGOvCBEIygBCdIwQpa8IIYzKAGN8jBDnrwgyAMoQhHSMISmvCEKEyhClfIwha68IUwjKEMZ0jDGtrwhuVKAAA7');
114 break;
115 case 'tableheader-bg.gif':
116 sendImage('R0lGODlhrgsyALMAAOPs8urw9O3y9eHr8t/q8t7q8uXt8+fu8+Xu8+ju8+fu9N7p8t3p8tzp8gAAAAAAACH5BAAAAAAALAAAAACuCzIAAAT/UMhJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsuFBr7gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipnQcHCaysCgqur7OwCQqts7WtsK+srrKuu7e+xbzFur+7r7i0xsfQxgm3zc6/vczT1tG+y7jCss+xyM7Vz8zHzbPqreHC69fa2gfh2/TE88PY5fLd5tIAj80rZm7ZtYPPpo3rZs1csG0GEfYCF3EhRHrnCKY7yO6WsFzY/96BvMcw27dkDck9HPaPnEtdFUu65NaN2kx42WrpPOeNl0dx/3a2FLexKEWfOEXOqydR1kmJOPl9pDXU3kGYRK0ijKjM4tZ993ZKlDmxXcWg5TL6+hay7U+RGJGOZBrN6Uh9caVGI5bRIMp+JO3ZzVcS7Up/av1ejakVGtdcXh2Dffg1ocajxiILVYsuqVt3c+PBpbtrMEqOKgH/qjpWoLKsrSXztCkzatiLltf6pMsM7TXWuj3HPTpa9Fy8tp7uBcvrMNXEAbFmJos7IW3cDgeKDdjZLNCbedWyHf7541LjS5HfU47XsOp20F++nt54u7TrlbNv59oTM0HfzXFWFv955L0VmlzpkWUaVMwB8x5fPEX32jrqLbhceM09CJxiAjEWm33I4CebftXNVNZPmQEIjIAYCdcRaOchSKGC+JxWlF4sxVeSdBrV11hXKk6m3Y/9eZcieMB1R+CL5nkmz4yC1cgghg5OtZqO1vCom4+xAQleeJTJxt2JvMESZJIDslMgjE6OVqGUF7pnJXx9SThSjx/+CFmQYA7ZZZEo/ocki+OpyaRcBbpJI3uFpTYnhLnt+JoBlFKKgKWYVmrApZp2Wimnm4Ka6aaZImAqqaKO6mmoo6aqqauncirqpa6Sumqnp75q66eyyvopq7jeamuqpvoq7K+51prrrckGe2z/q8T+Omysu0rLrLHVOvsqrapy62mt30ZLbK+tAqsrs6qGiu2xtK7LK7vugqvtsuVOyy2s8Na7KqzezpptutcGO66l2Lb7rbDxNvssqu4SnG+28p5rbr293nswwgUjHO6wuiq78MSo4krurgZre/G4De8LrLIRK2wtu+9KbGyxIZu8cczRLgwqvSSXSi2+GOt78bYcI6uyzuKK7HDPPP8rLcoAq+wy0UFD/PHOORdNs7cS75sxukqvy3XXVRtNtcslk30u1E4fPHXMAVutM9wUE2yxzUr3DDbRYjsdMd0145xx0/ImnLLbKTd9Nt5Hg7yzz3ervXjUAFNb7dgvl43p/8ArIwv00Wz/vXniLcd7NeCP2xt45nkvnTXMS1s7889Dey003pb76zHSAueNNshtF83r4QKT/vDrGsdet7qrU+4666zn3nHjD5steLmEJx/66W8rfzPye/OcesXNB9+58/wKX775qEM88uPZg7393N0DPzn0Q4v/tN3ll3672tKznuRuJsCVDe5zJxMZ8TpmvKqBr3H6KxX/MGe+XAHgghjMoAY3yMEOevCDIAyhCEdIwhKa8IQoTKEKV8jCFrrwhTCMoQxnSMMa2vCGOMyhDnfIwx768IdADKIQh0jEIhrxiEhMohKXyMQmOvGJUIyiFKdIxSpa8YpYzKIWt8jFLv968YtgDGMVB0DGMprxjGhMoxrXyMY2uvGNcIyjHOdIxzra8Y54zKMe98jHPvrxj4AMpCAHSchCGvKQiEykIhfJyEY68pGQjKQkJ0nJSlrykpjMpCY3yclOevKToAylKEdJylKa8pSoTKUqV8nKVrpSlASIpSxnScta2vKWuMylLnfJy1768pfADKYwZ1mAYRrzmMhMpjKXycxm8rKYzoymNKdJzWpak5jXzKY2t8nNbUKzm+AMpzjHSc5ufrOc6EynOqd5znW6853w9GU740nPetJznvbMpz73qU988vOfAKWmPwNK0IIic6AGTahCn7lQcRagAAt4qEQjKlGIVvT/ohjNKEUzytGOatSjIA2pSEdK0pJydKMbNelEU2pRlYaUpSw1aUxdelKa2vSmOBUpSnG6AJjm9KMVnSlJhfpTov70qEh96UR56tOkPrSpNDUqT51K1ap2dKc37elFpRrVrWbVqi29qldtylWygvWsVsUqWaGaVLaqtKxdRatckarWqLr1qHctKVxdute5+lWsT2XqWJ2a16GCta9/TWxQl5rVwk51sWZNK0gdq9PDKvayemXsWgfbVs6+1bKYDe1kNWtXz+LVtJmVrGhXC9SwlhayhEWtYVXL2toG9rabhW1ndftZ2l6UAcANrnCHS9ziGve4yE2ucpfL3OY697nQ/42udKdL3epa97rYza52t8vd7nr3u+ANr3jHS97ymve86E2vetfL3va6973wja9850vf+tr3vvjNr373y9/++ve/AA6wgAdM4AIb+MAITrCC/duABjv4wRCOsIQnTOEKW/jCGM6whjfM4Q57+MMgDrGIR0ziEpv4xChOsYpXzOIWu/jFMI6xjGdM4xrb+MY4zrGOd8zjHvv4x0AOspCHTOQiG/nISE6ykpfM5CY7+clQjrKUp0zlKlv5yktecHcbUFwui9fLWg6zdcEsXDJ/18xiTvNz0cwANnPXzWqOM3LZDGft1lnOeA4zncd75zznec9f9rOgjQvo8PZ50GouNP94D43oRjO4y3x2dKMVfWZJI5rS3mW0pROM6S1vWr9YDrWoR03qUpv61KhOtapXzepWu/rVsI61rGdN61rb+ta4zrWud83rXvv618AOtrCHTexiG/vYyE62spfN7GY7+9nQjra0p03talv72tjOtra3ze1ue/vb4A63uMdN7nKb+9zoTre6183udrv73fCOt7znTe962/ve+M63vvfN7377+98AD7jAB07wghv84AhPuMIXzvCGO/zhEI+4xCdO8Ypb/OIYz7jGN87xjnv84yAPuchHTvKSm/zkKE+5ylfO8pa7/OUwj7nMZ07zmtv85jjPuc53zvOe+/znQA+60If/TvSiG/3oSE+60pfO9KY7/elQj7rUp071qlv96ljPuta3zvWue/3rYA+72MdO9rKb/exoT7va1872trv97XCPu9znTve62/3ueM+73vfO9777/e+AD7zgB0/4whv+8IhPvOIXz/jGO/7xkI+85CdP+cpb/vKYz7zmN8/5znv+86APvehHT/rSm/70qE+96lfP+ta7/vWwj73sZ0/72tv+9rjPve53z/ve+/73wA++8IdP/OIb//jIT77yl8/85jv/+dCPvvSnT/3qW//62M++9rfP/e57//vgD7/4x0/+8pv//OhPv/rXz/72u//98I+//OdP//rb//74z7/+98//Yf77//8AGIACOIAEWIAGeIAImIAKuIAM2IAO+IAQGIESOIEUWIEWeIEYmIEauIEc2IEe+IEgGIIiOIIkWIImeIIomIIquIIs2IIu+IIwGIMyOIM0WIM2eIM4mIM6uIMvFwEAOw==');
117 break;
118 case 'top_left.gif':
119 sendImage('R0lGODlhCgAyAMQAAKvWa5rNTYjFL4XDKYTCKYTBKIPBKIPAKIK/KIG+J4G+KIC9J4C8J3+7J366Jn66J365Jn24Jny2Jnu2Jny3Jnu1JXq0JQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAAAKADIAAAV/ICCOZFkGaKoKbOsScCwbdG0jeK4nfO8zwKCwQSwaH8ikMsJsOinQqHRCrVot2Kx2u614v+BwWEIum89nqXqtdrrf8DhkTq877vi8cc/fC/+AfwuDhIWGhgqJiouMjDqPkJGSB5SVlpeXBZqbnJ2dMqChoAOkpaanqKmqq6ynIQA7');
120 break;
121 case 'loading.gif':
122 sendImage('R0lGODlhEAAQAPIAAP///2ZmZtra2o2NjWZmZqCgoLOzs729vSH+GkNyZWF0ZWQgd2l0aCBhamF4bG9hZC5pbmZvACH5BAAKAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQACgABACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkEAAoAAgAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkEAAoAAwAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkEAAoABAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQACgAFACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQACgAGACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAAKAAcALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==');
123 break;
124 }
125
126}
127
128
129/*****************************************************************
130* FUNCTIONS
131******************************************************************/
132
133# Quote string
134function quote($str) {
135 $str = str_replace('\\', '\\\\', $str);
136 $str = str_replace("'", "\'", $str);
137 return "'$str'";
138}
139
140# Convert filesize from bytes to most suitable unit
141function formatSize($bytes) {
142
143 # Define suitable units in 1024x increments
144 $types = array( 'B', 'KB', 'MB', 'GB', 'TB' );
145
146 # Decrease until we run out of units or we're less than 1024 in the current unit
147 for ( $i = 0, $l = count($types)-1; $bytes >= 1024 && $i < $l; $bytes /= 1024, $i++ );
148
149 # Return a rounded figure with unit
150 return ( round($bytes, 2) . ' ' . $types[$i] );
151
152}
153
154# Convert path to URL
155function pathToURL($filePath) {
156
157 # Run through realpath to normalise path
158 $realPath = realpath($filePath);
159
160 # Verify that the path passed is real and find the directory
161 if ( is_file($realPath)) {
162
163 $dir = dirname($realPath);
164
165 } elseif ( is_dir($realPath) ) {
166
167 $dir = $realPath;
168
169 } else {
170 # Path does not exist, fails
171 return false;
172 }
173
174 # Expand the document root path
175 $_SERVER['DOCUMENT_ROOT'] = realpath($_SERVER['DOCUMENT_ROOT']);
176
177 # Make sure the path is not lower than the server root
178 if ( strlen($dir) < strlen($_SERVER['DOCUMENT_ROOT']) ) {
179 return false;
180 }
181
182 # Determine path from web root
183 $rootPos = strlen($_SERVER['DOCUMENT_ROOT']);
184
185 # Make sure $rootPos includes the first slash
186 if ( ( $tmp = substr($_SERVER['DOCUMENT_ROOT'], -1) ) && ( $tmp == '/' || $tmp == '\\' ) ) {
187 --$rootPos;
188 }
189
190 # Extract path below webroot and discard path above webroot
191 $pathFromRoot = substr($realPath, $rootPos);
192
193 # Build URL from parts
194 $path = 'http' . ( isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off' ? 's' : '' ) . '://' . $_SERVER['HTTP_HOST']. $pathFromRoot;
195
196 # Convert to forward slash if on Windows
197 if ( DIRECTORY_SEPARATOR == '\\' ) {
198 $path = str_replace('\\', '/', $path);
199 }
200
201 return $path;
202}
203
204# Hide from non-js browsers by using document.write() to output
205function jsWrite($str) {
206 return '<script type="text/javascript">document.write(' . quote($str) . ');</script>';
207}
208
209# Convert a string of bool value to bool
210function bool($str) {
211 if ( $str == 'false' ) {
212 return false;
213 }
214 if ( $str == 'true' ) {
215 return true;
216 }
217 return NULL;
218}
219
220
221/*****************************************************************
222* CLASSES
223******************************************************************/
224
225/*****************************************************************
226* Location wrapper - allows us to have observers on the location
227******************************************************************/
228
229class Location {
230
231 # Observers
232 private $observers;
233
234 # Redirect elsewhere
235 public function redirect($to = '') {
236
237 # Notify observers
238 $this->notifyObservers('redirect');
239
240 # Redirect and quit
241 header('Location: ' . ADMIN_URI . '?' . $to);
242 exit;
243 }
244
245 # Redirect elsewhere but without observer
246 public function cleanRedirect($to = '') {
247
248 # Redirect and quit
249 header('Location: ' . ADMIN_URI . '?' . $to);
250 exit;
251 }
252
253 # Register observers
254 public function addObserver(&$obj) {
255 $this->observers[] = $obj;
256 }
257
258 # Notify observers
259 public function notifyObservers($action) {
260
261 # Determine method to call
262 $method = 'on' . ucfirst($action);
263
264 # Prepare parameters
265 $params = func_get_args();
266 array_shift($params);
267
268 # Loop through all observers
269 foreach ( $this->observers as $obj ) {
270
271 # If an observing method exists, call it
272 if ( method_exists($obj, $method) ) {
273
274 call_user_func_array(array(&$obj, $method), $params);
275
276 }
277
278 }
279
280 }
281
282}
283
284
285/*****************************************************************
286* Input wrapper for incoming data
287******************************************************************/
288
289class Input {
290
291 # Set up inputs
292 public function __construct() {
293
294 $this->GET = $this->prepare($_GET);
295 $this->POST = $this->prepare($_POST);
296 $this->COOKIE = $this->prepare($_COOKIE);
297
298 }
299
300 # Return array with keys converted to lowercase and values cleaned
301 private function prepare($array) {
302
303 $return = array();
304
305 foreach ( $array as $key => $value ) {
306 $return[strtolower($key)] = self::clean($value);
307 }
308
309 return $return;
310
311 }
312
313 # Get an input - inputs can be requested in the form pVarName
314 # where VarName is (case insensitive) name of variable (duh!)
315 # and p denotes from _POST. G and C are also available.
316 public function __get($name) {
317
318 # Do we have a varname?
319 if ( ! isset($name[1]) ) {
320 return NULL;
321 }
322
323 # Split into GPC and VarName (case insensitive)
324 $from = strtolower($name[0]);
325 $var = strtolower(substr($name, 1));
326
327 # Define $from to target relationships
328 $targets = array('g' => $this->GET,
329 'p' => $this->POST,
330 'c' => $this->COOKIE);
331
332 # Look for the value and return it
333 if ( isset($targets[$from][$var]) ) {
334 return $targets[$from][$var];
335 }
336
337 # Not found, return false
338 return NULL;
339
340 }
341
342 # Clean a value
343 static public function clean($val) {
344
345 static $magicQuotes;
346
347 # What is our magic quotes setting?
348 if ( ! isset($magicQuotes) ) {
349 $magicQuotes = get_magic_quotes_gpc();
350 }
351
352 # What type is this?
353 switch ( true ) {
354 case is_string($val):
355
356 # Strip slashes and trim
357 if ( $magicQuotes ) {
358 $val = stripslashes($val);
359 }
360
361 $val = trim($val);
362
363 break;
364
365 case is_array($val):
366
367 $val = array_map(array('Input', 'clean'), $val);
368
369 break;
370
371 default:
372 return $val;
373 }
374
375 return $val;
376
377 }
378
379}
380
381
382/*****************************************************************
383* Output wrappers
384******************************************************************/
385
386# A simple overloading object
387class Overloader {
388
389 # Store variables in this array
390 protected $data;
391
392 # Set value (case insensitive)
393 public function __set($name, $value) {
394 $this->data[strtolower($name)] = $value;
395 }
396
397 # Get value (case insensitive)
398 public function __get($name) {
399 $name = strtolower($name);
400 return isset($this->data[$name]) ? $this->data[$name] : '';
401 }
402
403}
404
405# Base wrapper object
406abstract class Output extends Overloader {
407
408 # Full page to output
409 protected $output;
410
411 # Content only
412 protected $content;
413
414 # Array of observers
415 protected $observers = array();
416
417
418 # Output the page
419 final public function out() {
420
421 # Notify our observers we're about to print
422 $this->notifyObservers('print', $this);
423
424 # Wrap content in our wrapper
425 $this->wrap();
426
427 # Send headers
428 $this->sendHeaders();
429
430 # Send body
431 print $this->output;
432
433 # Page completed, finish
434 exit;
435
436 }
437
438
439 # Override this to send custom headers instead of default (html)
440 protected function sendHeaders() {}
441
442
443 # Wrapper for body content
444 protected function wrap() {
445 $this->output = $this->content;
446 }
447
448
449 # Add content
450 public function addContent($content) {
451 $this->content .= $content;
452 }
453
454
455 # Register observers
456 public function addObserver(&$obj) {
457 $this->observers[] = $obj;
458 }
459
460
461 # Notify observers
462 public function notifyObservers($action) {
463
464 # Determine method to call
465 $method = 'on' . ucfirst($action);
466
467 # Prepare parameters
468 $params = func_get_args();
469 array_shift($params);
470
471 # Loop through all observers
472 foreach ( $this->observers as $obj ) {
473
474 # If an observing method exists, call it
475 if ( method_exists($obj, $method) ) {
476
477 call_user_func_array(array(&$obj, $method), $params);
478
479 }
480
481 }
482
483 }
484
485
486 # Send status code
487 public function sendStatus($code) {
488 header(' ', true, $code);
489 }
490
491 # More overloading. Set value with key.
492 public function __call($func, $args) {
493 if ( substr($func, 0, 3) == 'add' && strlen($func) > 3 && ! isset($args[2]) ) {
494
495 # Saving with key or not?
496 if ( isset($args[1]) ) {
497 $this->data[strtolower(substr($func, 3))][$args[0]] = $args[1];
498 } else {
499 $this->data[strtolower(substr($func, 3))][] = $args[0];
500 }
501
502 }
503 }
504
505}
506
507# Output with our HTML skin
508class SkinOutput extends Output {
509
510 # Print all
511 private function printAll($name) {
512 $name = strtolower($name);
513 if ( isset($this->data[$name]) && is_array($this->data[$name]) ) {
514 foreach ( $this->data[$name] as $item ) {
515 echo $item;
516 }
517 }
518 }
519
520 # Wrap content in HTML skin
521 protected function wrap() {
522
523 # Prepare the "get image" path
524 $imgs = ADMIN_URI . '?image=';
525
526 # Self
527 $self = ADMIN_URI;
528
529 # Prepare date
530 $date = date('H:i, d F Y');
531
532 # Append "glype control panel" to title
533 $title = $this->title . ( $this->title ? ' : ' : '' ) . 'Glype control panel';
534
535 # Buffer so we can get this into a variable
536 ob_start();
537
538 # Print output
539 echo <<<OUT
540<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
541<html xmlns="http://www.w3.org/1999/xhtml" >
542<head>
543<title>{$title}</title>
544<meta http-equiv="content-type" content="text/html; charset=UTF-8">
545<script type="text/javascript">var offsetx=12;var offsety=8;function newelement(a){if(document.createElement){var b=document.createElement('div');b.id=a;with(b.style){display='none';position='absolute'}b.innerHTML=' ';document.body.appendChild(b)}}var ie5=(document.getElementById&&document.all);var ns6=(document.getElementById&&!document.all);var ua=navigator.userAgent.toLowerCase();var isapple=(ua.indexOf('applewebkit')!=-1?1:0);function getmouseposition(e){if(document.getElementById){var a=(document.compatMode&&document.compatMode!='BackCompat')?document.documentElement:document.body;pagex=(isapple==1?0:(ie5)?a.scrollLeft:window.pageXOffset);pagey=(isapple==1?0:(ie5)?a.scrollTop:window.pageYOffset);mousex=(ie5)?event.x:(ns6)?clientX=e.clientX:false;mousey=(ie5)?event.y:(ns6)?clientY=e.clientY:false;var b=document.getElementById('tooltip');b.style.left=(mousex+pagex+offsetx)+'px';b.style.top=(mousey+pagey+offsety)+'px'}}function tooltip(a){if(!document.getElementById('tooltip'))newelement('tooltip');var b=document.getElementById('tooltip');b.innerHTML=a;b.style.display='block';document.onmousemove=getmouseposition}function exit(){document.getElementById('tooltip').style.display='none'}window.domReadyFuncs=new Array();window.addDomReadyFunc=function(a){window.domReadyFuncs.push(a)};function init(){if(arguments.callee.done)return;arguments.callee.done=true;if(_timer)clearInterval(_timer);for(var i=0;i<window.domReadyFuncs.length;++i){try{window.domReadyFuncs[i]()}catch(ignore){}}};if(document.addEventListener){document.addEventListener("DOMContentLoaded",init,false)}/*@cc_on@*//*@if(@_win32)document.write("<script id=__ie_onload defer src=javascript:void(0)><\\\/script>");var script=document.getElementById("__ie_onload");script.onreadystatechange=function(){if(this.readyState=="complete"){init()}};/*@end@*/if(/WebKit/i.test(navigator.userAgent)){var _timer=setInterval(function(){if(/loaded|complete/.test(document.readyState)){init()}},10)}window.onload=init;if(!window.XMLHttpRequest){window.XMLHttpRequest=function(){return new ActiveXObject('Microsoft.XMLHTTP')}}function runAjax(a,b,c,d){var e=new XMLHttpRequest();var f=b?'POST':'GET';e.open(f,a,true);e.setRequestHeader("Content-Type","application/x-javascript;");e.onreadystatechange=function(){if(e.readyState==4&&e.status==200){if(e.responseText){c.call(d,e.responseText)}}};e.send(b)}</script>
546<script type="text/javascript">
547OUT;
548
549 # Add domReady javascript
550 if ( $this->domReady ) {
551 echo 'window.addDomReadyFunc(function(){', $this->printAll('domReady'), '});';
552 }
553
554 # Add other javascript
555 if ( $this->javascript ) {
556 echo $this->printAll('javascript');
557 }
558
559 echo <<<OUT
560</script>
561<style type="text/css">body{margin:0;font-size:62.5%;font-family:Verdana, Arial, Helvetica, sans-serif;padding:15px 0;background:#eee}#wrap{width:820px;margin:0 auto;background:url({$self}?image=bg.gif) top center repeat-y #FFF}#top_content{padding:0 10px}#topheader{padding:25px 15px 15px;margin:0 auto;background:url({$self}?image=top_left.gif) top left repeat-x #85C329}#rightheader{float:right;width:375px;height:40px;color:#FFF;text-align:right}#rightheader p{padding:35px 15px 0 0;margin:0;text-align:right}#title{padding:0;margin:0;font-size:2.5em;color:#FFF}#title span{font-size:0.5em;font-style:italic}#title a:link,#title a:visited{color:#FFF;text-decoration:none}#title a:hover{color:#E1F3C7}#navigation{background:#74A8F5;border-top:1px solid #fff;height:25px;clear:both}#navigation ul{padding:0;margin:0;list-style:none;font-size:1.1em;height:25px}#navigation ul li{display:inline}#navigation ul li a{color:#FFF;display:block;text-decoration:none;float:left;line-height:25px;padding:0 16px;border-right:1px solid #fff}#navigation ul li a:hover{background:#5494F3}#content{padding:15px;margin:0 auto;background:url({$self}?image=content_bg.gif) repeat-x left top #fff;color:#666}#content h1,#content h2,#content h3,#content h4,#content h5{color:#74A8F5}#content h1{font-family:"Trebuchet MS", Arial, Helvetica;padding:0;margin:0 0 15px;font-size:2em}#content h2{font-family:"Trebuchet MS", Arial, Helvetica;padding:0;margin:0 0 15px;font-size:1.5em}#top_body,#content_body{padding:0 25px}#footer{background:url({$self}?image=footer.gif) no-repeat center bottom;color:#FFF;padding:0 10px 13px}#footer p a:link,#footer p a:visited{color:#FFF;font-style:italic;text-decoration:none}#footer #footer_bg{background:url({$self}?image=footer_bg.gif) repeat-x left bottom #85C329;padding:15px 15px 25px;border-top:1px solid #7BB425}#footer #design{display:block;width:150px;height:30px;float:right;line-height:20px;padding:0 5px;text-align:right;color:#E1F3C7}#footer #design a,#rightheader a:link,#rightheader a:visited{color:#FFF;text-decoration:underline}.table{margin-bottom:15px;width:100%;border-collapse:collapse}.table_header td a:link,.table_header td a:visited{text-decoration:underline;color:#467aa7}.table_header td{background:url({$self}?image=tableheader-bg.gif) no-repeat left top;padding:5px 10px;color:#467aa7;border-top:1px solid #CBD6DE;border-bottom:1px solid #ADBECB;font-size:1.1em;font-weight:bold;border:1px solid #CBD6DE}.row1 td,.row2 td,.row3 td,.row_hover td,.paging_row td{padding:5px 10px;color:#666;border:1px solid #CBD6DE}.row1 td{background:#fff}.row2 td{background:#f6f6f6}.row3 td{background:#eee}.row1:hover td,.row2:hover td,.row3:hover td{background:#FBFACE;color:#000}.hidden{display:none}#content .little{font-size:9px}.clear{clear:both}.img_left{float:left;padding:1px;border:1px solid #ccc;margin:0 10px 10px 0}#content ul{font-size:1.1em;line-height:1.8em;margin:0 0 15px;padding:0;list-style-type:none}#content p{font-size:1.2em;margin:0;padding:0 0 15px;line-height:150%}#content p a:hover,.table a:hover,.form_table a:hover,.link a:hover{text-decoration:underline}#content ul.green li{padding:0 0 0 20px;margin:0;background:url({$self}?image=bullet_green.gif) no-repeat 1px 3px;font-size:1.1em}#content ul.black li{padding:0 0 0 20px;margin:0;background:url({$self}?image=bullet_grey.gif) no-repeat 1px 3px;font-size:1.1em}#content ul.black li a:link,#content ul.black li a:visited{color:#666;text-decoration:none}#content ol{padding:0 0 0 25px;margin:0 0 15px;line-height:1.8em}#content ol li{font-size:1.1em}#content ol li a:link,#content ol li a:visited,#content ul.green li a:link,#content ul.green li a:visited,#content p a,#content p a:visited,.table a,.table a:visited,.form_table a,.link a{color:#73A822;text-decoration:none}#content ol li a:hover,#content ul.green li a:hover,.table_header td a:hover{color:#73A822;text-decoration:underline}#content p.paging{padding:5px;border:1px solid #CBD6DE;text-align:center;margin-bottom:15px;background:#eee}.small_input{font-size:10px}.form_table{margin-bottom:15px;font-size:1.1em}.form_table td{padding:5px 10px}input.button{margin:0;padding:2px;border:3px double #999;border-left-color:#ccc;border-top-color:#ccc;background:url({$self}?image=button.gif) repeat-x left top;font-size:11px;font-family:Verdana, Arial, Helvetica, sans-serif}input.inputgri,select.inputgri,textarea.inputgri{background:#eee;font-size:14px;border:1px solid #ccc;padding:3px}input.inputgri:focus,select.inputgri:focus,textarea.inputgri:focus{background:#fff;border:1px solid #686868}textarea.inputgri{font-size:12px;font-family:Verdana, Arial, Helvetica, sans-serif;height:60px}.notice{background:#CAEA99;border:1px solid #70A522;padding:15px;margin-bottom:15px;font-size:1.2em;color:#333}.notice_error{background:#FEDCDA;border:1px solid #CE090E;padding:15px;margin-bottom:15px;font-size:1.2em;color:#333}.notice .close,.notice_error .close{float:right;cursor:pointer;color:#fff;background:#74A8F5;padding:2px;margin-right:2px;border:1px outset #ccc}#notice a{color:#333;text-decoration:underline}.other_links{background:#eee;border-top:1px solid #ccc;padding:5px;margin:0 0 15px}#content .other_links h2{color:#999;padding:0 0 0 3px;margin:0}#content .other_links ul li{padding:0 0 0 20px;background:url({$self}?image=bullet_grey.gif) no-repeat left center}#content .other_links a,#content .other_links a:visited,#content ul.black li a:hover{color:#999;text-decoration:underline}#content .other_links a:hover{color:#666}code{font-size:1.2em;color:#73A822}#tooltip{width:20em;color:#fff;background:#555;font-size:12px;font-weight:normal;padding:5px;border:3px solid #333;text-align:left}.hr{border-top:2px solid #ccc;margin:5px 0 15px}.bold,#rightheader p span{font-weight:bold}.center{text-align:center}.right{text-align:right}.error-color{color:#CE090E}.ok-color{color:#70A522}.wide-input{width:350px}.small-input{width:50px}.tooltip{padding-bottom:1px;border-bottom:1px dotted #70A522;cursor:help}.ajax-loading{background:url({$self}?image=loading.gif)}.bar{background:#73A822;height:10px;font-size:xx-small;padding:2px;color:#000}.comment{padding:5px;border:1px solid #CBD6DE;border-width:1px 0 1px 0;margin-bottom:15px;background:#f6f6f6}#content .comment p,#content .comment ul,#content .other_links ul,form,.checkbox_nomargins,.form_table p,#footer p{margin:0;padding:0}#preload{position:absolute;height:10px;top:-100px}</style>
562</head>
563<body>
564 <div id="wrap">
565
566 <div id="top_content">
567
568 <div id="header">
569
570 <div id="rightheader">
571 <p>
572 {$date}
573 <br />
574OUT;
575
576 # Add the "welcome" and log out link
577 if ( $this->admin ) {
578 echo "welcome, <i>{$this->admin}</i> : <strong><a href=\"{$self}?logout\">log out</a></strong>\r\n";
579 }
580
581 $http_host = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
582 echo <<<OUT
583 </p>
584 </div>
585
586 <div id="topheader">
587 <h1 id="title">
588 <a href="{$self}">Glype Admin Control Panel</a><br>
589 <span>for {$http_host}</span>
590 </h1>
591 </div>
592
593 <div id="navigation">
594 <ul>
595OUT;
596
597 # Add navigation
598 if ( is_array($this->navigation) ) {
599
600 foreach ( $this->navigation as $text => $href ) {
601 if (stripos($href,$self)!==false) {
602 echo "<li><a href=\"{$href}\">{$text}</a></li>\r\n";
603 } else {
604 echo "<li><a href=\"{$href}\" target=\"_blank\">{$text}</a></li>\r\n";
605 }
606 }
607
608 }
609
610 echo <<<OUT
611 </ul>
612 </div>
613
614 </div>
615
616 <div id="content">
617
618 <h1>{$this->bodyTitle}</h1>
619OUT;
620
621 # Do we have any error messages?
622 if ( $this->error ) {
623
624 # Print all
625 foreach ( $this->error as $id => $message ) {
626 echo <<<OUT
627 <div class="notice_error" id="notice_error_{$id}">
628 <a class="close" title="Dismiss" onclick="document.getElementById('notice_error_{$id}').style.display='none';">X</a>
629 {$message}
630 </div>
631OUT;
632 }
633
634 }
635
636 # Do we have any confirmation messages?
637 if ( $this->confirm ) {
638
639 # Print all
640 foreach ( $this->confirm as $id => $message ) {
641 echo <<<OUT
642 <div class="notice" id="notice_{$id}">
643 <a class="close" title="Dismiss" onclick="document.getElementById('notice_{$id}').style.display='none';">X</a>
644 {$message}
645 </div>
646OUT;
647 }
648
649 }
650
651 # Print content
652 echo $this->content;
653
654 # Print footer links
655 if ( is_array($this->footerLinks) ) {
656
657 echo '
658 <br>
659 <div class="other_links">
660 <h2>See also</h2>
661 <ul class="other">
662 ';
663
664 foreach ( $this->footerLinks as $text => $href ) {
665 echo "<li><a href=\"{$href}\">{$text}</a></li>\r\n";
666 }
667
668 echo '
669 </ul>
670 </div>
671 ';
672
673 }
674
675
676 # And finish off the page
677 echo <<<OUT
678
679 </div>
680
681 </div>
682
683 <div id="footer">
684
685 <div id="footer_bg">
686 <p><a href="http://www.glype.com/">Glype</a>® © 2007-2015 Glype. All rights reserved.</p>
687 </div>
688
689 </div>
690
691 </div>
692
693 <div id="preload">
694 <span class="ajax-loading"> </span>
695 </div>
696
697</body>
698</html>
699OUT;
700
701 $this->output = ob_get_contents();
702
703 # Discard buffer
704 ob_end_clean();
705
706 }
707
708}
709
710
711# Send output in "raw" form
712class RawOutput extends Output {
713
714 protected function sendHeaders() {
715 header('Content-Type: text/plain; charset="utf-8"');
716 header('Content-Disposition: inline; filename=""');
717 }
718
719}
720
721
722/*****************************************************************
723* User object
724******************************************************************/
725
726# Manage sessions and stores user data
727class User {
728
729 # Username we're logged in as
730 public $name;
731
732 # Our user agent
733 public $userAgent;
734
735 # Our IP address
736 public $IP;
737
738 # Reason for aborting a session
739 public $aborted;
740
741
742 # Constructor sets up session
743 public function __construct() {
744
745 # Don't try to start if autostarted
746 if ( session_id() == '' ) {
747
748 # Set up new session
749 session_name('admin');
750 session_start();
751
752 }
753
754 # Always use a fresh ID for security
755 session_regenerate_id();
756
757 # Prepare user data
758 $this->userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
759 $this->IP = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
760
761 # Use user-agent and IP as identifying data since these shouldn't change mid-session
762 $authKey = $this->userAgent . $this->IP;
763
764 # Do we have a stored auth key?
765 if ( isset($_SESSION['auth_key']) ) {
766
767 # Compare our current auth_key to stored key
768 if ( $_SESSION['auth_key'] != $authKey ) {
769
770 # Mismatch. Session may be stolen.
771 $this->clear();
772 $this->aborted = 'Session data mismatch.';
773
774 }
775
776 } else {
777
778 # No stored auth key, save it
779 $_SESSION['auth_key'] = $authKey;
780
781 }
782
783 # Are we verified?
784 if ( ! empty($_SESSION['verified']) ) {
785 $this->name = $_SESSION['verified'];
786 }
787
788 # Have we expired? Only expire if we're logged in of course...
789 if ( $this->isAdmin() && isset($_SESSION['last_click']) && $_SESSION['last_click'] < (time() - ADMIN_TIMEOUT) ) {
790 $this->clear();
791 $this->aborted = 'Your session timed out after ' . round(ADMIN_TIMEOUT/60) . ' minutes of inactivity.';
792 }
793
794 # Set last click time
795 $_SESSION['last_click'] = time();
796
797 }
798
799 # Log out, destroy all session data
800 public function clear() {
801
802 # Clear existing
803 session_destroy();
804
805 # Unset existing variables
806 $_SESSION = array();
807 $this->name = false;
808
809 # Restart session
810 session_start();
811
812 }
813
814 # Log in, saving username session for future requests
815 public function login($name) {
816 $this->name = $name;
817 $_SESSION['verified'] = $name;
818 }
819
820 # Are we verified or not?
821 public function isAdmin() {
822 return (bool) $this->name;
823 }
824
825}
826
827
828/*****************************************************************
829* Notice handler (errors or confirmations)
830******************************************************************/
831
832class Notice {
833
834 # Storage of messages
835 private $data = array();
836
837 # Type of notice handler
838 private $type;
839
840 # Constructor fetches any stored from session and clears session
841 public function __construct($type) {
842
843 # Save type
844 $this->type = $type;
845
846 # Array key
847 $key = 'notice_' . $type;
848
849 # Any existing?
850 if ( isset($_SESSION[$key]) ) {
851
852 # Extract
853 $this->data = $_SESSION[$key];
854
855 # And clear
856 unset($_SESSION[$key]);
857
858 }
859
860 }
861
862 # Get messages
863 public function get($id = false) {
864
865 # Requesting an individual message?
866 if ( $id !== false ) {
867 return isset($this->data[$id]) ? $this->data[$id] : false;
868 }
869
870 # Requesting all
871 return $this->data;
872
873 }
874
875 # Add message
876 public function add($msg, $id = false) {
877
878 # Add with or without an explicit key
879 if ( $id ) {
880 $this->data[$id] = $msg;
881 } else {
882 $this->data[] = $msg;
883 }
884
885 }
886
887 # Do we have any messages?
888 public function hasMsg() {
889 return ! empty($this->data);
890 }
891
892 # Observer the print method of output
893 public function onPrint($output) {
894
895 $funcName = 'add' . $this->type;
896
897 # Add our messages to the output object
898 foreach ( $this->data as $msg ) {
899 $output->{$funcName}($msg);
900 }
901
902 }
903
904 # Observe redirects - store notices in session
905 public function onRedirect() {
906 $_SESSION['notice_' . $this->type] = $this->data;
907 }
908
909}
910
911
912/*****************************************************************
913* Initialize instances of defined classes. If we were structing
914* this nicely we'd stick the above in separate files to keep it
915* clean but we're sacrificing good structure for the convenience
916* of running this admin script stand-alone.
917******************************************************************/
918
919# Create output object
920$output = new SkinOutput;
921
922# Create an overloader object to hold our template vars.
923# This keeps them all together and avoids problems with undefined variable notices.
924$tpl = new Overloader;
925
926# Location wrapper for redirections
927$location = new Location;
928
929# Create user object
930$user = new User();
931
932# Create notice handlers
933$confirm = new Notice('confirm');
934$error = new Notice('error');
935
936# Input wrapper
937$input = new Input;
938
939
940/*****************************************************************
941* Nearly finished preparing, now just bind them together as appropriate
942******************************************************************/
943
944# Add notice handlers as observers of the output object
945$output->addObserver($confirm);
946$output->addObserver($error);
947
948# Add notice handlers as observers on redirect();
949$location->addObserver($confirm);
950$location->addObserver($error);
951
952# Pass user details to output object
953$output->admin = $user->name;
954
955/*****************************************************************
956* AJAX INTERCEPTS
957******************************************************************/
958
959if ( $input->gFetch && $user->isAdmin() ) {
960
961 # Stop caching of response
962 header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
963 header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT' );
964 header('Cache-Control: no-cache, must-revalidate');
965 header('Pragma: no-cache');
966
967 switch ( $input->gFetch ) {
968
969 # Get the latest news
970 case 'news':
971
972 # Style the news
973 echo '<style type="text/css">body { margin:0; padding:5px; font:80% Tahoma,Verdana; } a { color: #73A822; }</style>';
974
975 # Connect to glype
976 if ($ch=curl_init('http://www.glype.com/feeds/news.php?vn='.urlencode($CONFIG['version']).'&lk='.urlencode($CONFIG['license_key']).'&cb='.$cache_bust)) {
977 curl_setopt($ch, CURLOPT_TIMEOUT, 2);
978 $success = curl_exec($ch);
979 curl_close($ch);
980 }
981
982 # Ensure we have a return
983 if ( empty($success) ) {
984 echo 'Currently unable to connect to glype.com for a news update.';
985 }
986
987 break;
988
989
990 # Verify a directory exists and is writable
991 case 'test-dir':
992
993 $fail = false;
994
995 # Verify
996 if ( ! ( $dir = $input->gDir ) ) {
997
998 # Check we have a dir to test
999 $fail = 'no directory given';
1000
1001 } else if ( ! file_exists($dir) || ! is_dir($dir) ) {
1002
1003 # Check it exists and is actually a directory
1004 $fail = 'directory does not exist';
1005
1006 # Try to create it (in case it was inside the temporary directory)
1007 if ( ! bool($input->gTmp) && is_writable(dirname($dir)) && @mkdir($dir, 0755, true) ) {
1008
1009 # Reset error messages and delete directory
1010 $fail = false;
1011 $ok = 'directory does not exist but can be created';
1012 rmdir($dir);
1013
1014 }
1015
1016 } else if ( ! is_writable($dir) ) {
1017
1018 # Make sure it's writable
1019 $fail = 'directory not writable - permission denied';
1020
1021 } else {
1022
1023 # OK
1024 $ok = 'directory exists and is writable';
1025
1026 }
1027
1028 # Print result
1029 if ( $fail ) {
1030 echo '<span class="error-color">Error:</span> ', $fail;
1031 } else {
1032 echo '<span class="ok-color">OK:</span> ', $ok;
1033 }
1034
1035 break;
1036
1037 }
1038
1039 # Finish here
1040 exit;
1041}
1042
1043
1044/*****************************************************************
1045* Did our settings file load? If not, nothing else we can do.
1046******************************************************************/
1047
1048if ( ! $settingsLoaded ) {
1049
1050 # Show error and exit
1051 $error->add('The settings file for Glype could not be found.
1052 Please upload this tool into your root glype directory.
1053 If you wish to run this script from another location,
1054 edit the configuration options at the top of the file.
1055 <br><br>
1056 Attempted to load: <b>' . ADMIN_GLYPE_SETTINGS . '</b>');
1057 $output->out();
1058
1059}
1060
1061
1062/*****************************************************************
1063* Verify a valid action and force to something else if not.
1064******************************************************************/
1065
1066# Are we an admin? If not, force login page.
1067if ( ! $user->isAdmin() ) {
1068 $action = 'login';
1069}
1070
1071# Do we even have any user details? If not, force installer.
1072if ( ! isset($adminDetails) ) {
1073 $action = 'install';
1074}
1075
1076
1077/*****************************************************************
1078* Prepare template variables
1079******************************************************************/
1080
1081# URI to self
1082$self = ADMIN_URI;
1083
1084# Links to other sections of the control panel
1085if ( $user->isAdmin() ) {
1086 $output->addNavigation('Home', $self);
1087 $output->addNavigation('Edit Settings', $self.'?settings');
1088 $output->addNavigation('View Logs', $self.'?logs');
1089 $output->addNavigation('Glype® Licenses', 'https://www.glype.com/purchase.php');
1090 $output->addNavigation('BlockScript®', $self.'?blockscript');
1091 $output->addNavigation('Support Forum', 'http://proxy.org/forum/glype-proxy/');
1092 $output->addNavigation('Promote Your Proxy', 'https://proxy.org/advertise.shtml');
1093}
1094
1095
1096/*****************************************************************
1097* Process current request.
1098******************************************************************/
1099
1100switch ( $action ) {
1101
1102
1103 /*****************************************************************
1104 * INSTALL - save an admin username/password in our settings file
1105 ******************************************************************/
1106
1107 case 'install':
1108
1109 # Do we have any admin details already?
1110 if ( isset($adminDetails) ) {
1111
1112 # Add error
1113 $error->add('An administrator account already exists. For security reasons, you must manually create additional administrator accounts.');
1114
1115 # And redirect to index
1116 $location->redirect();
1117
1118 }
1119
1120 # Do we have any submitted details to process?
1121 if ( $input->pSubmit ) {
1122
1123 # Verify inputs
1124 if ( ! ( $username = $input->pAdminUsername ) ) {
1125 $error->add('You must enter a username to protect access to your control panel!');
1126 }
1127
1128 if ( ! ( $password = $input->pAdminPassword ) ) {
1129 $error->add('You must enter a password to protect access to your control panel!');
1130 }
1131
1132 # In case things go wrong, add this into the template
1133 $tpl->username = $username;
1134
1135 # Process the installation if no errors
1136 if ( ! $error->hasMsg() && is_writable(ADMIN_GLYPE_SETTINGS) ) {
1137
1138 # Load up the file
1139 $file = file_get_contents(ADMIN_GLYPE_SETTINGS);
1140
1141 # Clear any closing php tag ? > (unnecessary and gets in the way)
1142 if ( substr(trim($file), -2) == '?>' ) {
1143 $file = substr(trim($file), 0, -2);
1144 }
1145
1146 # Look for a "Preserve Me" section
1147 if ( strpos($file, '//---PRESERVE ME---') === false ) {
1148
1149 # If it doesn't exist, add it
1150 $file .= "\r\n//---PRESERVE ME---
1151# Anything below this line will be preserved when the admin control panel rewrites
1152# the settings. Useful for storing settings that don't/can't be changed from the control panel\r\n";
1153
1154 }
1155
1156 # Prepare the inputs
1157 $password = md5($password);
1158
1159 # Add to file
1160 $file .= "\r\n\$adminDetails[" . quote($username) . "] = " . quote($password) . ";\r\n";
1161
1162 # Save updated file
1163 if ( file_put_contents(ADMIN_GLYPE_SETTINGS, $file) ) {
1164
1165 # Add confirmation
1166 $confirm->add('Installation successful. You have added <b>' . $username . '</b> as an administrator and are now logged in.');
1167
1168 # Log in the installer
1169 $user->login($username);
1170
1171 } else {
1172
1173 # Add error message
1174 $error->add('Installation failed. The settings file appears writable but file_put_contents() failed.');
1175
1176 }
1177
1178 # Redirect
1179 $location->redirect();
1180
1181 }
1182
1183 }
1184
1185 # Prepare skin variables
1186 $output->title = 'install';
1187 $output->bodyTitle = 'First time use installation';
1188
1189 # Add javascript
1190 $output->addDomReady("document.getElementById('username').focus();");
1191
1192 # Is the settings file writable?
1193 if ( ! ( $writable = is_writable(ADMIN_GLYPE_SETTINGS) ) ) {
1194
1195 $error->add('The settings file was found at <b>' . ADMIN_GLYPE_SETTINGS . '</b> but is not writable. Please set the appropriate permissions to make the settings file writable.');
1196
1197 # And disable the submit button
1198 $tpl->disabled = ' disabled="disabled"';
1199
1200 } else {
1201
1202 $confirm->add('Settings file was found and is writable. Installation can proceed. <b>Do not leave the script at this stage!</b>');
1203
1204 }
1205
1206 # Print form
1207 echo <<<OUT
1208 <p>No administrator details were found in the settings file. Enter a username and password below to continue. The details supplied will be required on all future attempts to use this control panel.</p>
1209
1210 <form action="{$self}?install" method="post">
1211 <table class="form_table" border="0" cellpadding="0" cellspacing="0">
1212 <tr>
1213 <td align="right">Username:</td>
1214 <td align="left"><input class="inputgri" id="username" name="adminUsername" type="text" value="{$tpl->username}"></td>
1215 </tr>
1216 <tr>
1217 <td align="right">Password:</td>
1218 <td align="left"><input class="inputgri" name="adminPassword" type="password"></td>
1219 </tr>
1220 </table>
1221 <p><input class="button" value="Submit »" name="submit" type="submit"{$tpl->disabled}></p>
1222 </form>
1223
1224OUT;
1225 break;
1226
1227
1228 /*****************************************************************
1229 * LOG IN
1230 ******************************************************************/
1231
1232 case 'login':
1233
1234 # Do we have any login details to process?
1235 if ( $input->pLoginSubmit ) {
1236
1237 # Verify inputs
1238 if ( ! ( $username = $input->pAdminUsername ) ) {
1239 $error->add('You did not enter your username. Please try again.');
1240 }
1241
1242 if ( ! ( $password = $input->pAdminPassword ) ) {
1243 $error->add('You did not enter your password. Please try again.');
1244 }
1245
1246 # Validate the submitted details
1247 if ( ! $error->hasMsg() ) {
1248
1249 # Validate submitted password
1250 if ( isset($adminDetails[$username]) && $adminDetails[$username] == md5($password) ) {
1251
1252 # Update user
1253 $user->login($username);
1254
1255 # Redirect to index
1256 $location->cleanRedirect();
1257
1258 } else {
1259
1260 # Incorrect password
1261 $error->add('The login details you submitted were incorrect.');
1262
1263 }
1264
1265 }
1266
1267 }
1268
1269 # Have we been automatically logged out?
1270 if ( $user->aborted ) {
1271 $error->add($user->aborted);
1272 }
1273
1274 # Set up page titles
1275 $output->title = 'log in';
1276 $output->bodyTitle = 'Log in';
1277
1278 # Add javascript
1279 $output->addDomReady("document.getElementById('username').focus();");
1280
1281 # Show form
1282 echo <<<OUT
1283 <p>This is a restricted area for authorised users only. Enter your log in details below.</p>
1284 <form action="{$self}?login" method="post">
1285 <table class="form_table" border="0" cellpadding="0" cellspacing="0">
1286 <tr>
1287 <td align="right">Username:</td>
1288 <td align="left"><input class="inputgri" id="username" name="adminUsername" type="text"></td>
1289 </tr>
1290 <tr>
1291 <td align="right">Password:</td>
1292 <td align="left"><input class="inputgri" name="adminPassword" type="password"></td>
1293 </tr>
1294 </table>
1295 <p><input class="button" value="Submit »" name="loginsubmit" type="submit"></p>
1296 </form>
1297OUT;
1298
1299 break;
1300
1301
1302 /*****************************************************************
1303 * LOG OUT
1304 ******************************************************************/
1305
1306 case 'logout':
1307
1308 # Clear all user data
1309 $user->clear();
1310
1311 # Print confirmation
1312 $confirm->add('You are now logged out.');
1313
1314 # Redirect back to login page
1315 $location->redirect('login');
1316
1317 break;
1318
1319
1320 /*****************************************************************
1321 * INDEX - check status and print summary
1322 ******************************************************************/
1323
1324 case '':
1325
1326 #
1327 # System requirements
1328 #
1329
1330 $requirements = array();
1331
1332 # PHP VERSION ----------------------
1333 # Find PHP version - may be bundled OS so strip that out
1334 $phpVersion = ( $tmp = strpos(PHP_VERSION, '-') ) ? substr(PHP_VERSION, 0, $tmp) : PHP_VERSION;
1335
1336 # Check above 5 and if not, add error text
1337 if ( ! ( $ok = version_compare($phpVersion, '5', '>=') ) ) {
1338 $error->add('Glype requires at least PHP 5 or greater.');
1339 }
1340
1341 # Add to requirements
1342 $requirements[] = array(
1343 'name' => 'PHP version',
1344 'value' => $phpVersion,
1345 'ok' => $ok
1346 );
1347
1348 # CURL -------------------------------
1349 # Check for libcurl
1350 if ( ! ( $ok = function_exists('curl_version') ) ) {
1351 $error->add('Glype requires cURL/libcurl.');
1352 }
1353
1354 # curl version
1355 $curlVersion = $ok && ( $tmp = curl_version() ) ? $tmp['version'] : 'not available';
1356
1357 # Add to requirements
1358 $requirements[] = array(
1359 'name' => 'cURL version',
1360 'value' => $curlVersion,
1361 'ok' => $ok
1362 );
1363
1364 # --------------------------------------
1365
1366 # Print page header
1367 $output->bodyTitle = 'Welcome to your control panel';
1368
1369 #
1370 # Glype news
1371 #
1372
1373 echo <<<OUT
1374 <p>This script provides an easy to use interface for managing your Glype. Use the navigation above to get started.</p>
1375 <h2>Latest Glype news...</h2>
1376 <iframe scrolling="no" src="{$self}?fetch=news" style="width: 100%; height:150px; border: 1px solid #ccc;" onload="setTimeout('updateLatestVersion()',1000);"></iframe>
1377 <br><br>
1378
1379 <h2>Checking environment...</h2>
1380 <ul class="green">
1381OUT;
1382
1383 # Print requirements
1384 foreach ( $requirements as $li ) {
1385 echo "<li>{$li['name']}: <span class=\"bold" . ( ! $li['ok'] ? ' error-color' : '' ) . "\">{$li['value']}</span></li>\r\n";
1386 }
1387
1388 # End requirements
1389 echo <<<OUT
1390 </ul>
1391OUT;
1392
1393 # How are we doing - tell user if we're OK or not.
1394 if ( $error->hasMsg() ) {
1395 echo '<p><span class="bold error-color">Environment check failed</span>. You will not be able to run Glype until you fix the above issue(s).</p>';
1396 } else {
1397 echo '<p><span class="bold ok-color">Environment okay</span>. You can run Glype on this server.</p>';
1398 }
1399
1400
1401 #
1402 # Script versions
1403 #
1404
1405 $acpVersion = ADMIN_VERSION;
1406 $proxyVersion = isset($CONFIG['version']) ? $CONFIG['version'] : 'unknown - pre 1.0';
1407
1408 # Create javascript to update the latest stable version
1409 $javascript = <<<OUT
1410 function updateLatestVersion(response) {
1411 document.getElementById('current-version').innerHTML = '<img src="http://www.glype.com/feeds/proxy-version.php?cb={$cache_bust}" border="0" alt="version" />';
1412 }
1413OUT;
1414 $output->addJavascript($javascript);
1415
1416 # Print version summary
1417 echo <<<OUT
1418 <br>
1419 <h2>Checking script versions...</h2>
1420 <ul class="green">
1421 <li>Control Panel version: <b>{$acpVersion}</b></li>
1422 <li>Glype version: <b>{$proxyVersion}</b></li>
1423 <li>Latest version: <span class="bold" id="current-version">unknown</span></li>
1424 </ul>
1425OUT;
1426
1427 # Is the settings file up to date?
1428 function forCompare($val) { return str_replace(' ', '', $val); }
1429
1430 if ( $proxyVersion != 'unknown - pre 1.0' && version_compare(forCompare($acpVersion), forCompare($proxyVersion), '>') ) {
1431 echo "<p><span class=\"bold error-color\">Note:</span> Your settings file needs updating. Use the <a href=\"{$self}?settings\">Edit Settings</a> page and click Update.</p>";
1432 }
1433
1434
1435 # Add footer links
1436 $output->addFooterLinks('Glype support forum at Proxy.org', 'http://proxy.org/forum/glype-proxy/');
1437
1438 break;
1439
1440
1441 /*****************************************************************
1442 * SETTINGS
1443 ******************************************************************/
1444
1445 case 'settings':
1446
1447 # Check the settings are writable
1448 if ( ! is_writable(ADMIN_GLYPE_SETTINGS) ) {
1449 $error->add('The settings file is not writable. You will not be able to save any changes. Please set permissions to allow PHP to write to <b>' . realpath(ADMIN_GLYPE_SETTINGS) . '</b>');
1450 $tpl->disabled = ' disabled="disabled"';
1451 }
1452
1453 # Load options into object
1454 $options = simplexml_load_string('<?xml version="1.0" encoding="UTF-8"?><options><section name="Special Options" type="settings"><option key="license_key" type="string" input="text" styles="wide-input"><title>Glype License key</title><default>\'\'</default><desc>If you have purchased a license, please enter your license key here. Leave blank if you don\'t have a license.</desc></option><option key="enable_blockscript" type="bool" input="radio"><title>Enable BlockScript</title><default>false</default><desc>BlockScript is security software which protects websites and empowers webmasters to stop unwanted traffic.</desc></option></section><section name="Installation Options" type="settings"><option key="theme" type="string" input="select"><title>Theme</title><default>\'default\'</default><desc>Theme/skin to use. This should be the name of the appropriate folder inside the /themes/ folder.</desc><generateOptions eval="true"><![CDATA[/* Check the dir exists */$themeDir = GLYPE_ROOT . \'/themes/\';if ( ! is_dir($themeDir) ) {return false;}/* Load folders from /themes/ */$dirs = scandir($themeDir);/* Loop through to create options string */$options = \'\';foreach ( $dirs as $dir ) {/* Ignore dotfiles */if ( $dir[0] == \'.\' ) {continue;}/* Add if this is valid theme */if ( file_exists($themeDir . $dir . \'/main.php\') ) {/* Make selected if this is our current theme */$selected = ( isset($currentValue) && $currentValue == $dir ) ? \' selected="selected"\' : \'\';/* Add option */$options .= "<option{$selected}>{$dir}</option>";}}return $options;]]></generateOptions></option><option key="plugins" type="string" input="text" styles="wide-input" readonly="readonly"><title>Register Plugins</title><default></default><desc>Run plugins on these websites</desc><toDisplay eval="true"><![CDATA[ if ($handle = opendir(GLYPE_ROOT."/plugins")) {while (($plugin=readdir($handle))!==false) {if (preg_match(\'#\.php$#\', $plugin)) {$plugin = preg_replace("#\.php$#", "", $plugin);$plugins[] = $plugin;}}closedir($handle);$plugin_list = implode(",", $plugins);} return $plugin_list; ]]></toDisplay><afterField>Auto-generated from plugins directory. Do not edit!</afterField></option><option key="tmp_dir" type="string" input="text" styles="wide-input"><title>Temporary directory</title><default>GLYPE_ROOT . \'/tmp/\'</default><desc>Temporary directory used by the script. Many features require write permission to the temporary directory. Ensure this directory exists and is writable for best performance.</desc><relative to="GLYPE_ROOT" desc="root proxy folder" /><isDir /></option><option key="gzip_return" type="bool" input="radio"><title>Use GZIP compression</title><default>false</default><desc>Use GZIP compression when sending pages back to the user. This reduces bandwidth usage but at the cost of increased CPU load.</desc></option><option key="ssl_warning" type="bool" input="radio"><title>SSL warning</title><default>true</default><desc>Warn users before browsing a secure site if on an insecure connection. This option has no effect if your proxy is on https.</desc></option><option key="override_javascript" type="bool" input="radio"><title>Override native javascript</title><default>false</default><desc>The fastest and most reliable method of ensuring javascript is properly proxied is to override the native javascript functions with our own. However, this may interfere with any other javascript added to the page, such as ad codes.</desc></option><option key="load_limit" type="float" input="text" styles="small-input"><title>Load limiter</title><default>0</default><desc>This option fetches the server load and stops the script serving pages whenever the server load goes over the limit specified. Set to 0 to disable this feature.</desc><afterField eval="true"><![CDATA[/* Attempt to find the load */$load = ( ($uptime = @shell_exec(\'uptime\')) && preg_match(\'#load average: ([0-9.]+),#\', $uptime, $tmp) ) ? (float) $tmp[1] : false;if ( $load === false ) {return \'<span class="error-color">Feature unavailable here</span>. Failed to find current server load.\';} else {return \'<span class="ok-color">Feature available here</span>. Current load: \' . $load;}]]></afterField></option><option key="footer_include" type="string" input="textarea" styles="wide-input"><title>Footer include</title><default>\'\'</default><desc>Anything specified here will be added to the bottom of all proxied pages just before the <![CDATA[</body>]]> tag.</desc><toDisplay eval="true"><![CDATA[ return htmlentities($currentValue); ]]></toDisplay></option></section><section name="URL Encoding Options" type="settings"><option key="path_info_urls" type="bool" input="radio"><title>Use path info</title><default>false</default><desc>Formats URLs as browse.php/aHR0... instead of browse.php?u=aHR0... Path info may not be available on all servers.</desc></option></section><section name="Hotlinking" type="settings"><option key="stop_hotlinking" type="bool" input="radio"><title>Prevent hotlinking</title><default>true</default><desc>This option prevents users "hotlinking" directly to a proxied page and forces all users to first visit the index page. Note: hotlinking is also prevented when the "Encrypt URL" option is enabled.</desc></option><option key="hotlink_domains" type="array" input="textarea" styles="wide-input"><title>Allow hotlinking from</title><default>array()</default><desc>If the above option is enabled, you can add individual referrers that are allowed to bypass the hotlinking protection. Note: hotlinking is also prevented when the "Encrypt URL" option is enabled.</desc><toDisplay eval="true"><![CDATA[ return implode("\r\n", $currentValue); ]]></toDisplay><toStore eval="true"><![CDATA[ $value = str_replace("\r", "\n", $value);$value=preg_replace("#\n+#", "\n", $value);return array_filter(explode("\n", $value));]]></toStore><afterField>Enter one domain per line</afterField></option></section><section name="Logging" type="settings"><comment><![CDATA[<p>You may be held responsible for requests from your proxy\'s IP address. You can use logs to record the decrypted URLs of pages visited by users in case of illegal activity undertaken through your proxy.</p>]]></comment><option key="enable_logging" type="bool" input="radio"><title>Enable logging</title><default>false</default><desc>Enable/disable the logging feature. If disabled, skip the rest of this section.</desc></option><option key="logging_destination" type="string" input="text" styles="wide-input"><title>Path to log folder</title><default>$CONFIG[\'tmp_dir\'] . \'logs/\'</default><desc>Enter a destination for log files. A new log file will be created each day in the directory specified. The directory must be writable. To protect against unauthorized access, place the log folder above your webroot.</desc><relative to="$CONFIG[\'tmp_dir\']" desc="temporary directory" /><isDir /></option><option key="log_all" type="bool" input="radio"><title>Log all requests</title><default>false</default><desc>You can avoid huge log files by only logging requests for .html pages, as per the default setting. If you want to log all requests (images, etc.) as well, enable this.</desc></option></section><section name="Website access control" type="settings"><comment><![CDATA[<p>You can restrict access to websites through your proxy with either a whitelist or a blacklist:</p><ul class="black"><li>Whitelist: any site that <strong>is not</strong> on the list will be blocked.</li><li>Blacklist: any site that <strong>is</strong> on the list will be blocked</li></ul>]]></comment><option key="whitelist" type="array" input="textarea" styles="wide-input"><title>Whitelist</title><default>array()</default><desc>Block everything except these websites</desc><toDisplay eval="true"><![CDATA[ return implode("\r\n", $currentValue); ]]></toDisplay><toStore eval="true"><![CDATA[ $value = str_replace("\r", "\n", $value);$value=preg_replace("#\n+#", "\n", $value);return array_filter(explode("\n", $value));]]></toStore><afterField>Enter one domain per line</afterField></option><option key="blacklist" type="array" input="textarea" styles="wide-input"><title>Blacklist</title><default>array()</default><desc>Block these websites</desc><toDisplay eval="true"><![CDATA[ return implode("\r\n", $currentValue); ]]></toDisplay><toStore eval="true"><![CDATA[ $value = str_replace("\r", "\n", $value);$value=preg_replace("#\n+#", "\n", $value);return array_filter(explode("\n", $value));]]></toStore><afterField>Enter one domain per line</afterField></option></section><section name="User access control" type="settings"><comment><![CDATA[<p>You can ban users from accessing your proxy by IP address. You can specify individual IP addresses or IP address ranges in the following formats:</p><ul class="black"><li>127.0.0.1</li><li>127.0.0.1-127.0.0.5</li><li>127.0.0.1/255.255.255.255</li><li>192.168.17.1/16</li><li>189.128/11</li></ul>]]></comment><option key="ip_bans" type="array" input="textarea" styles="wide-input"><title>IP bans</title><default>array()</default><toDisplay eval="true"><![CDATA[ return implode("\r\n", $currentValue); ]]></toDisplay><toStore eval="true"><![CDATA[ $value = str_replace("\r", "\n", $value);$value=preg_replace("#\n+#", "\n", $value);return array_filter(explode("\n", $value));]]></toStore><afterField>Enter one IP address or IP address range per line</afterField></option></section><section name="Transfer options" type="settings"><option key="connection_timeout" type="int" input="text" styles="small-input" unit="seconds"><title>Connection timeout</title><default>5</default><desc>Time to wait for while establishing a connection to the target server. If the connection takes longer, the transfer will be aborted.</desc><afterField>Use 0 for no limit</afterField></option><option key="transfer_timeout" type="int" input="text" styles="small-input" unit="seconds"><title>Transfer timeout</title><default>15</default><desc>Time to allow for the entire transfer. You will need a longer time limit to download larger files.</desc><afterField>Use 0 for no limit</afterField></option><option key="max_filesize" type="int" input="text" styles="small-input" unit="MB"><title>Filesize limit</title><default>0</default><desc>Preserve bandwidth by limiting the size of files that can be downloaded through your proxy.</desc><toDisplay>return $currentValue ? round($currentValue/(1024*1024), 2) : 0;</toDisplay><toStore>return $value*1024*1024;</toStore><afterField>Use 0 for no limit</afterField></option><option key="download_speed_limit" type="int" input="text" styles="small-input" unit="KB/s"><title>Download speed limit</title><default>0</default><desc>Preserve bandwidth by limiting the speed at which files are downloaded through your proxy. Note: if limiting download speed, you may need to increase the transfer timeout to compensate.</desc><toDisplay>return $currentValue ? round($currentValue/(1024), 2) : 0;</toDisplay><toStore>return $value*1024;</toStore><afterField>Use 0 for no limit</afterField></option><option key="resume_transfers" type="bool" input="radio"><title>Resume transfers</title><default>false</default><desc>This forwards any requested ranges from the client and this makes it possible to resume previous downloads. Depending on the "Queue transfers" option below, it may also allow users to download multiple segments of a file simultaneously.</desc></option><option key="queue_transfers" type="bool" input="radio"><title>Queue transfers</title><default>true</default><desc>You can limit use of your proxy to allow only one transfer at a time per user. Disable this for faster browsing.</desc></option></section><section name="Cookies" type="settings"><comment><![CDATA[<p>All cookies must be sent to the proxy script. The script can then choose the correct cookies to forward to the target server. However there are finite limits in both the client\'s storage space and the size of the request Cookie: header that the server will accept. For prolonged browsing, you may wish to store cookies server side to avoid this problem.</p><br><p>This has obvious privacy issues - if using this option, ensure your site clearly states how it handles cookies and protect the cookie data from unauthorized access.</p>]]></comment><option key="cookies_on_server" type="bool" input="radio"><title>Store cookies on server</title><default>false</default><desc>If enabled, cookies will be stored in the folder specified below.</desc></option><option key="cookies_folder" type="string" input="text" styles="wide-input"><title>Path to cookie folder</title><default>$CONFIG[\'tmp_dir\'] . \'cookies/\'</default><desc>If storing cookies on the server, specify a folder to save the cookie data in. To protect against unauthorized access, place the cookie folder above your webroot.</desc><relative to="$CONFIG[\'tmp_dir\']" desc="temporary directory" /><isDir /></option><option key="encode_cookies" type="bool" input="radio"><title>Encode cookies</title><default>false</default><desc>You can encode cookie names, domains and values with this option for optimum privacy but at the cost of increased server load and larger cookie sizes. This option has no effect if storing cookies on server.</desc></option></section><section name="Maintenance" type="settings"><option key="tmp_cleanup_interval" type="float" input="text" styles="small-input" unit="hours"><title>Cleanup interval</title><default>48</default><desc>How often to clear the temporary files created by the script?</desc><afterField>Use 0 to disable</afterField></option><option key="tmp_cleanup_logs" type="float" input="text" styles="small-input" unit="days"><title>Keep logs for</title><default>30</default><desc>When should old log files be deleted? This option has no effect if the above option is disabled.</desc><afterField>Use 0 to never delete logs</afterField></option></section><section type="user" name="User Configurable Options"><option key="encodeURL" default="true" force="false"><title>Encrypt URL</title><desc>Encrypts the URL of the page you are viewing for increased privacy. Note: this option is intended to obscure URLs and does not provide security. Use SSL for actual security.</desc></option><option key="encodePage" default="false" force="false"><title>Encrypt Page</title><desc>Helps avoid filters by encrypting the page before sending it and decrypting it with javascript once received. Note: this option is intended to obscure HTML source code and does not provide security. Use SSL for actual security.</desc></option><option key="showForm" default="true" force="true"><title>Show Form</title><desc>This provides a mini-form at the top of each page that allows you to quickly jump to another site without returning to our homepage.</desc></option><option key="allowCookies" default="true" force="false"><title>Allow Cookies</title><desc>Cookies may be required on interactive websites (especially where you need to log in) but advertisers also use cookies to track your browsing habits.</desc></option><option key="tempCookies" default="true" force="true"><title>Force Temporary Cookies</title><desc>This option overrides the expiry date for all cookies and sets it to at the end of the session only - all cookies will be deleted when you shut your browser. (Recommended)</desc></option><option key="stripTitle" default="false" force="true"><title>Remove Page Titles</title><desc>Removes titles from proxied pages.</desc></option><option key="stripJS" default="true" force="false"><title>Remove Scripts</title><desc>Remove scripts to protect your anonymity and speed up page loads. However, not all sites will provide an HTML-only alternative. (Recommended)</desc></option><option key="stripObjects" default="false" force="false"><title>Remove Objects</title><desc>You can increase page load times by removing unnecessary Flash, Java and other objects. If not removed, these may also compromise your anonymity.</desc></option></section><section type="forced" hidden="true" name="Do not edit this section manually!"><option key="version" type="string"><default>\''.ADMIN_VERSION.'\'</default><desc>Settings file version for determining compatibility with admin tool.</desc></option></section></options>');
1455
1456 #
1457 # SAVE CHANGES
1458 #
1459 if ( $input->pSubmit && ! $error->hasMsg() ) {
1460
1461 # Filter inputs to create valid PHP code
1462 function filter($value, $type) {
1463
1464 switch ( $type ) {
1465
1466 # Quote strings
1467 case 'string':
1468 default:
1469 return quote($value);
1470
1471 # Clean integers
1472 case 'int':
1473 return intval($value);
1474
1475 # Float
1476 case 'float':
1477 if ( is_numeric($value) ) {
1478 return $value;
1479 }
1480 return quote($value);
1481
1482 # Create arrays - make empty array if no value, not an array with a single empty value
1483 case 'array':
1484 $args = $value ? implode(', ', array_map('quote', (array) $value)) : '';
1485 return 'array(' . $args . ')';
1486
1487 # Bool - check we have a real bool and resort to default if not
1488 case 'bool':
1489 if ( bool($value) === NULL ) {
1490 global $option;
1491 $value = $option->default;
1492 }
1493 return $value;
1494
1495 }
1496
1497 }
1498
1499 # Create a comment line
1500 function comment($text, $multi=false) {
1501
1502 # Comment marker
1503 $char = $multi ? '*' : '#';
1504
1505 # Split and make newlines with the comment char
1506 $text = wordwrap($text, 65, "\r\n$char ");
1507
1508 # Return a large comment
1509 if ( $multi ) {
1510 return '/*****************************************************************
1511* ' . $text . '
1512******************************************************************/';
1513 }
1514
1515 # Return a small comment
1516 return "# $text";
1517
1518 }
1519
1520 # Prepare the file header
1521 $toWrite = '<?php
1522/*******************************************************************
1523* Glype is copyright and trademark 2007-2015 UpsideOut, Inc. d/b/a Glype
1524* and/or its licensors, successors and assigners. All rights reserved.
1525*
1526* Use of Glype is subject to the terms of the Software License Agreement.
1527* http://www.glype.com/license.php
1528*******************************************************************
1529* Our settings file. Self-explanatory - stores the config values.
1530*******************************************************************
1531* This file has been automatically generated by the glype admin tool.
1532* For a more complete and thorough explanation of options, consult
1533* the original settings.php file from the glype package.
1534******************************************************************/
1535';
1536 # Loop through all the sections
1537 foreach ( $options->section as $section ) {
1538
1539 # Add section header to the file
1540 $toWrite .= NL . NL . comment($section['name'], true) . NL;
1541
1542 # Now go through this section's options
1543 foreach ( $section->option as $option ) {
1544
1545 $key = (string) $option['key'];
1546
1547 # Grab the posted value
1548 $value = $input->{'p' . $key};
1549
1550 # The user-configurable options need special treatment
1551 if ( $section['type'] == 'user' ) {
1552
1553 # We need to save 4 values - title, desc, default and force
1554 $title = filter( ( isset($value['title']) ? $value['title'] : $option->title ), 'string');
1555 $desc = filter( ( isset($value['desc']) ? $value['desc'] : $option->desc ), 'string');
1556 $default = filter( ( isset($value['default']) ? $value['default'] : $option['default']), 'bool');
1557 $force = isset($value['force']) ? 'true' : 'false';
1558
1559 # Write them
1560 $toWrite .=
1561"\r\n\$CONFIG['options'][" . quote($key) . "] = array(
1562 'title' => $title,
1563 'desc' => $desc,
1564 'default' => $default,
1565 'force' => $force
1566);\r\n";
1567
1568 # Finished saving, move to next
1569 continue;
1570 }
1571
1572 # Do we have a posted value or is it forced?
1573 if ( $value === NULL || $section['forced'] ) {
1574
1575 # Resort to default (which comes ready quoted)
1576 $value = $option->default;
1577
1578 } else {
1579
1580 # Yes, apply quotes and any pre-storage logic
1581 if ( $option->toStore && ($tmp = @eval($option->toStore)) ) {
1582 $value = $tmp;
1583 }
1584
1585 # Normalize directory paths
1586 if ( $option->isDir ) {
1587
1588 # Use forward slash only
1589 $value = str_replace('\\', '/', $value);
1590
1591 # Add trailing slash
1592 if ( substr($value, -1) && substr($value, -1) != '/' ) {
1593 $value .= '/';
1594 }
1595 }
1596
1597 # Filter it according to desired var type
1598 $value = filter($value, $option['type']);
1599
1600 # Add any relativeness
1601 if ( $option->relative && $input->{'pRelative_' . $key} ) {
1602 $value = $option->relative['to'] . ' . ' . $value;
1603 }
1604
1605 }
1606
1607 # Add to file (commented description and $CONFIG value)
1608 $toWrite .= NL . comment($option->desc) . NL;
1609 if ($key=='enable_blockscript' && $value=='true') {
1610 $value='false';
1611 if (function_exists('ioncube_loader_version')&&file_exists($_SERVER['DOCUMENT_ROOT'].'/blockscript/detector.php')) {$value='true';}
1612 }
1613 $toWrite .= '$CONFIG[' . quote($key) . '] = ' . $value . ';' . NL;
1614
1615 }
1616
1617 }
1618
1619 # Extract any preserved details
1620 $file = file_get_contents(ADMIN_GLYPE_SETTINGS);
1621
1622 # And add to file
1623 if ( $tmp = strpos($file, '//---PRESERVE ME---') ) {
1624 $toWrite .= NL . substr($file, $tmp);
1625 }
1626
1627 # Finished, save to file
1628 if ( file_put_contents(ADMIN_GLYPE_SETTINGS, $toWrite) ) {
1629 $confirm->add('The settings file has been updated.');
1630 } else {
1631 $error->add('The settings file failed to write. The file was detected as writable but file_put_contents() returned false.');
1632 }
1633
1634 # And redirect to reload the new settings
1635 $location->redirect('settings');
1636
1637 }
1638
1639 #
1640 # SHOW FORM
1641 #
1642
1643 # Set up page variables
1644 $output->title = 'edit settings';
1645 $output->bodyTitle = 'Edit settings';
1646
1647 # Print form
1648 echo <<<OUT
1649 <p>This page allows you to edit your configuration to customize and tweak your proxy. If an option is unclear, hover over the option name for a more detailed description. <a href="#notes">More...</a></p>
1650
1651 <form action="{$self}?settings" method="post">
1652OUT;
1653
1654 # Add an "Update" button. Functionally identical to "Save".
1655 function forCompare($val) { return str_replace(' ', '', $val); }
1656
1657 if ( empty($CONFIG['version']) || version_compare(forCompare(ADMIN_VERSION), forCompare($CONFIG['version']), '>') ) {
1658 echo '<p class="error-color">Your settings file needs updating. <input class="button" type="submit" name="submit" value="Update »"></p>';
1659 }
1660
1661 # Add the javascript for this page
1662 $javascript = <<<OUT
1663 // Create ajax "loading" image
1664 window.loadingImage = '<img src="{$self}?image=loading.gif" width="16" height="16" alt="loading...">';
1665
1666 // Toggle "relative from root" option
1667 function toggleRelative(checkbox, textId) {
1668
1669 var textField = document.getElementById(textId);
1670 var relative = checkbox.value;
1671
1672 // Are we adding or taking away?
1673 if ( ! checkbox.checked ) {
1674
1675 // Does the field already contain the relative path?
1676 if ( textField.value.indexOf(relative) != 0 ) {
1677 textField.value = relative += textField.value;
1678 }
1679
1680 } else {
1681 textField.value = textField.value.replace(relative, '');
1682 }
1683
1684 }
1685
1686 // Check if a given directory exists / is_writable
1687 var testDir = function(fieldName) {
1688
1689 // Save vars in object
1690 this.input = document.getElementById(fieldName);
1691 this.result = document.getElementById('dircheck_' + fieldName);
1692 this.relative = document.getElementById('relative_' + fieldName);
1693 this.isTmp = fieldName == 'tmp_dir';
1694
1695 // Update status
1696 this.updateDirStatus = function(response) {
1697 this.result.innerHTML = response;
1698 }
1699
1700 // Run when value is changed
1701 this.changed = function() {
1702
1703 this.isRelative = this.relative ? this.relative.checked : false;
1704
1705 // Attempt to get path from the value
1706 var dirPath = this.input.value;
1707
1708 // Is it relative?
1709 if ( this.isRelative ) {
1710 dirPath = this.relative.value + dirPath;
1711 }
1712
1713 // Update with the loading .gif
1714 this.result.innerHTML = loadingImage;
1715
1716 // Make the request
1717 runAjax('$self?fetch=test-dir&dir=' + encodeURIComponent(dirPath) + '&tmp=' + this.isTmp, null, this.updateDirStatus, this);
1718
1719 }
1720
1721 }
1722
1723OUT;
1724 $output->addJavascript($javascript);
1725
1726 # Go through all options and print the form
1727 foreach ( $options->section as $section ) {
1728
1729 # Print title if we're displaying this
1730 if ( $section['hidden'] === NULL ) {
1731
1732 echo '
1733 <br>
1734 <div class="hr"></div>
1735 <h2>' . $section['name'] . '</h2>';
1736
1737 }
1738
1739 # What type of section is this?
1740 switch ( $section['type'] ) {
1741
1742 # Standard option/value pairs
1743 case 'settings':
1744
1745 # Comment
1746 if ( $section->comment ) {
1747 echo '<div class="comment">',$section->comment,'</div>';
1748 }
1749
1750 # Print table header
1751 echo <<<OUT
1752 <table class="form_table" border="0" cellpadding="0" cellspacing="0">
1753
1754OUT;
1755
1756 # Loop through the child options
1757 foreach ( $section->option as $option ) {
1758
1759 # Reset variables
1760 $field = '';
1761
1762 # Convert to string so we can use it as an array index
1763 $key = (string) $option['key'];
1764
1765 # Find current value (if we have one)
1766 $currentValue = isset($CONFIG[$key]) ? $CONFIG[$key] : @eval('return ' . $option->default . ';');
1767
1768 # If the option can be relative, find out what we're relative from
1769 if ( $option->relative ) {
1770
1771 # Run code from options XML to get value
1772 $relativeTo = @eval('return ' . $option->relative['to'] . ';');
1773
1774 # Remove that from the current value
1775 $currentValue = str_replace($relativeTo, '', $currentValue, $relativeChecked);
1776
1777 }
1778
1779 # If the option has any "toDisplay" filtering, apply it
1780 if ( $option->toDisplay && ( $newValue = @eval($option->toDisplay) ) !== false ) {
1781 $currentValue = $newValue;
1782 }
1783
1784 # Create attributes (these are fairly consistent in multiple option types)
1785 $attr = <<<OUT
1786 type="{$option['input']}" name="{$option['key']}" id="{$option['key']}" value="{$currentValue}" class="inputgri {$option['styles']}"
1787OUT;
1788
1789 # Prepare the input
1790 switch ( $option['input'] ) {
1791
1792 # TEXT FIELD
1793 case 'text':
1794
1795 # Add onchange to test dirs
1796 if ( $option->isDir ) {
1797 $attr .= " onchange=\"test{$option['key']}.changed()\"";
1798 }
1799
1800 $field = '<input' . $attr . '>';
1801
1802 # Can we be relative to another variable?
1803 if ( $option->relative ) {
1804
1805 # Is the box already checked?
1806 $checked = empty($relativeChecked) ? '' : ' checked="checked"';
1807
1808 # Escape backslashes so we can use it in javascript
1809 $relativeToEscaped = str_replace('\\', '\\\\', $relativeTo);
1810
1811 # Add to existing field
1812 $field .= <<<OUT
1813<input type="checkbox" onclick="toggleRelative(this,'{$option['key']}')" value="{$relativeTo}" name="relative_{$option['key']}" id="relative_{$option['key']}"{$checked}>
1814<label class="tooltip" for="relative_{$option['key']}" onmouseover="tooltip('You can specify the value as relative to the {$option->relative['desc']}:<br><b>{$relativeToEscaped}</b>')" onmouseout="exit();">Relative to {$option->relative['desc']}</label>
1815OUT;
1816 }
1817 break;
1818
1819 # SELECT FIELD
1820 case 'select':
1821 $field = '<select' . $attr . '>' . @eval($option->generateOptions). '</select>';
1822 break;
1823
1824 # RADIO
1825 case 'radio':
1826 $onChecked = $currentValue ? ' checked="checked"' : '';
1827 $offChecked = ! $currentValue ? ' checked="checked"' : '';
1828
1829 $field = <<<OUT
1830<input type="radio" name="{$option['key']}" id="{$option['key']}_on" value="true" class="inputgri {$option['styles']}"{$onChecked}>
1831<label for="{$option['key']}_on">Yes</label>
1832 /
1833<input type="radio" name="{$option['key']}" id="{$option['key']}_off" value="false" class="inputgri {$option['styles']}"{$offChecked}>
1834<label for="{$option['key']}_off">No</label>
1835OUT;
1836 break;
1837
1838 # TEXTAREA
1839 case 'textarea':
1840 $field = '<textarea ' . $attr . '>' . $currentValue . '</textarea><br>';
1841 break;
1842
1843 }
1844
1845 # Is there a description to use as tooltip?
1846 $tooltip = $option->desc ? 'class="tooltip" onmouseover="tooltip(\'' . htmlentities(addslashes($option->desc), ENT_QUOTES) . '\')" onmouseout="exit()"' : '';
1847
1848 # Add units
1849 if ( $option['unit'] ) {
1850 $field .= ' ' . $option['unit'];
1851 }
1852
1853 # Any after field text to add?
1854 if ( $option->afterField ) {
1855
1856 # Code to eval or string?
1857 $add = $option->afterField['eval'] ? @eval($option->afterField) : $option->afterField;
1858
1859 # Add to field
1860 if ( $add ) {
1861 $field .= ' (<span class="little">' . $add . '</span>)';
1862 }
1863
1864 }
1865
1866 echo <<<OUT
1867 <tr>
1868 <td width="160" align="right">
1869 <label for="{$option['key']}" {$tooltip}>{$option->title}:</label>
1870 </td>
1871 <td>{$field}</td>
1872 </tr>
1873
1874OUT;
1875
1876 # Is this a directory path we're expecting?
1877 if ( $option->isDir ) {
1878
1879 # Write with javascript to hide from non-js browsers
1880 $write = jsWrite('(<a style="cursor:pointer;" onclick="test' . $option['key'] . '.changed()">try again</a>)');
1881
1882 echo <<<OUT
1883 <tr>
1884 <td> </td>
1885 <td>
1886
1887 <span id="dircheck_{$option['key']}"></span>
1888 $write
1889 </td>
1890 </tr>
1891
1892OUT;
1893 $output->addDomReady("window.test{$option['key']} = new testDir('{$option['key']}');test{$option['key']}.changed();");
1894 }
1895
1896 }
1897
1898 echo '</table>';
1899
1900 break;
1901
1902
1903 # User configurable options
1904 case 'user':
1905
1906 # Print table header
1907 echo <<<OUT
1908 <table class="table" cellpadding="0" cellspacing="0">
1909 <tr class="table_header">
1910 <td width="200">Title</td>
1911 <td width="50">Default</td>
1912 <td>Description</td>
1913 <td width="50">Force <span class="tooltip" onmouseover="tooltip('Forced options do not appear on the proxy form and will always use the default value')" onmouseout="exit()">?</span></td>
1914 </tr>
1915OUT;
1916 # Find the current options
1917 $currentOptions = isset($CONFIG['options']) ? $CONFIG['options'] : array();
1918
1919 # Print options
1920 foreach ( $section->option as $option ) {
1921
1922 # Get values from XML
1923 $key = (string) $option['key'];
1924
1925 # Get values from current settings, resorted to XML if not available
1926 $title = isset($currentOptions[$key]['title']) ? $currentOptions[$key]['title'] : $option->title;
1927 $default = isset($currentOptions[$key]['default']) ? $currentOptions[$key]['default'] : bool($option['default']);
1928 $desc = isset($currentOptions[$key]['desc']) ? $currentOptions[$key]['desc'] : $option->desc;
1929 $force = isset($currentOptions[$key]['force']) ? $currentOptions[$key]['force'] : bool($option['force']);
1930
1931 # Determine checkboxes
1932 $on = $default == true ? ' checked="checked"' : '';
1933 $off = $default == false ? ' checked="checked"' : '';
1934 $force = $force ? ' checked="checked"' : '';
1935
1936 # Row color
1937 $row = isset($row) && $row == 'row1' ? 'row2' : 'row1';
1938
1939 echo <<<OUT
1940 <tr class="{$row}">
1941 <td><input type="text" class="inputgri" style="width:95%;" name="{$key}[title]" value="{$title}"></td>
1942 <td>
1943 <input type="radio" name="{$key}[default]" value="true" id="options_{$key}_on"{$on}> <label for="options_{$key}_on">On</label>
1944 <br>
1945 <input type="radio" name="{$key}[default]" value="false" id="options_{$key}_off"{$off}> <label for="options_{$key}_off">Off</label>
1946 </td>
1947 <td><textarea class="inputgri wide-input" name="{$key}[desc]">{$desc}</textarea></td>
1948 <td><input type="checkbox" name="{$key}[force]"{$force} value="true"></td>
1949 </tr>
1950
1951OUT;
1952 }
1953
1954 # Print table footer
1955 echo '</table>';
1956
1957 break;
1958
1959 }
1960
1961 }
1962
1963 # Page footer
1964 echo <<<OUT
1965 <div class="hr"></div>
1966 <p class="center"><input class="button" type="submit" name="submit" value="Save Changes"{$tpl->disabled}></p>
1967 </form>
1968
1969 <div class="hr"></div>
1970 <h2><a name="notes"></a>Notes:</h2>
1971 <ul class="black">
1972 <li><b>Temporary directory:</b> many features require write access to the temporary directory. Ensure you set up the permissions accordingly if you use any of these features: logging, server-side cookies, maintenance/cleanup and the server load limiter.</li>
1973 <li><b>Sensitive data:</b> some temporary files may contain personal data that should be kept private - that is, log files and server-side cookies. If using these features, protect against unauthorized access by choosing a suitable location above the webroot or using .htaccess files to deny access.</li>
1974 <li><b>Relative paths:</b> you can specify some paths as relative to other paths. For example, if logs are created in /[tmp_dir]/logs/ (as per the default setting), you can edit the value for tmp_dir and the logs path will automatically update.</li>
1975 <li><b>Quick start:</b> by default, all temporary files are created in the /tmp/ directory. Subfolders for features are created as needed. Private files are protected with .htaccess files. If running Apache, you can simply set writable permissions on the /tmp/ directory (0755 or 0777) and all features will work without further changes to the filesystem or permissions.</li>
1976 </ul>
1977 <p class="right"><a href="#">^ top</a></p>
1978OUT;
1979
1980 break;
1981
1982
1983 /*****************************************************************
1984 * BlockScript
1985 ******************************************************************/
1986 case 'blockscript':
1987 if (file_exists($bsc=$_SERVER['DOCUMENT_ROOT'].'/blockscript/tmp/config.php')) {
1988 include($bsc);
1989 # header('Location: /blockscript/detector.php?blockscript=setup&bsap='.$BS_VAL['admin_password']); exit;
1990 }
1991 $installed = isset($BS_VAL['license_agreement_accepted']) ? '<span class="ok-color">installed</span>' : '<span class="error-color">not installed</span>';
1992 $enabled = (isset($BS_VAL['license_agreement_accepted']) && !empty($CONFIG['enable_blockscript'])) ? '<span class="ok-color">enabled</span>' : '<span class="error-color">disabled</span>';
1993
1994 if (!($ok=function_exists('ioncube_loader_version'))) {$error->add('BlockScript requires IonCube.');}
1995 $IonCubeVersion = $ok && ( $tmp = ioncube_loader_version() ) ? $tmp : 'not available';
1996 if ($ok && $tmp!='not available') {
1997
1998 }
1999
2000 # Print header
2001 $output->title = 'BlockScript®';
2002 $output->bodyTitle = 'BlockScript® Integration';
2003
2004 echo <<<OUT
2005 <form action="{$self}?blockscript" method="post">
2006 <table class="form_table" border="0" cellpadding="0" cellspacing="0">
2007 <tr>
2008 <td align="right">BlockScript status:</td>
2009 <td><b>{$installed} and {$enabled}</b></td>
2010 </tr>
2011 </table>
2012 </form>
2013 <div class="hr"></div>
2014 <h2>About</h2>
2015 <p><a href="https://www.blockscript.com/" target="_blank">BlockScript</a> is security software which protects websites and empowers webmasters to stop unwanted traffic. BlockScript detects and blocks requests from all types of proxy servers and anonymity networks, hosting networks, undesirable robots and spiders, and even entire countries.</p>
2016
2017 <p>BlockScript can help proxy websites by blocking filtering company spiders and other bots. BlockScript detects and blocks: barracudanetworks.com, bluecoat.com, covenanteyes.com, emeraldshield.com, ironport.com, lightspeedsystems.com, mxlogic.com, n2h2.com, netsweeper.com, securecomputing.com, mcafee.com, sonicwall.com, stbernard.com, surfcontrol.com, symantec.com, thebarriergroup.com, websense.com, and much more.</p>
2018
2019 <p>BlockScript provides free access to core features and <a href="https://www.blockscript.com/pricing.php" target="_blank">purchasing a license key</a> unlocks all features. A one week free trial is provided so that you can fully evaluate all features of the software.</p>
2020
2021 <div class="hr"></div>
2022 <h2>Installation Instructions</h2>
2023 <ol>
2024 <li><a href="https://www.blockscript.com/download.php" target="_blank">Download BlockScript</a> and extract the contents of the .zip file.</li>
2025 <li>Upload the "blockscript" directory and its contents.</li>
2026 <li>CHMOD 0777 (or 0755 if running under suPHP) the "detector.php" file and the "/blockscript/tmp/" directory.</li>
2027 <li>Visit <a href="http://{$_SERVER['HTTP_HOST']}/blockscript/detector.php" target="_blank">http://{$_SERVER['HTTP_HOST']}/blockscript/detector.php</a> in your browser.</li>
2028 <li>Follow the on-screen prompts in your BlockScript control panel.</li>
2029 </ol>
2030 <br>
2031
2032OUT;
2033 if ($bsc) {
2034 $admin_password = isset($BS_VAL['admin_password']) ? $BS_VAL['admin_password'] : '';
2035 echo '<div class="hr"></div><h2>Your BlockScript Installation</h2><p><a href="/blockscript/detector.php?blockscript=setup&bsap='.$admin_password.'" target="_blank">Login To Your BlockScript Control Panel</a></p>';
2036 }
2037 break;
2038
2039
2040 /*****************************************************************
2041 * LOG INDEX
2042 ******************************************************************/
2043 case 'logs':
2044
2045 # Are we updating the log destination?
2046 if ( $input->pDestination !== NULL ) {
2047
2048 # Attempt to validate path
2049 $path = realpath($input->pDestination);
2050
2051 # Is the path OK?
2052 if ( $path ) {
2053 $confirm->add('Log folder updated.');
2054 } else {
2055 $error->add('Log folder not updated. <b>' . $input->pDestination . '</b> does not exist.');
2056 }
2057
2058 # Normalize
2059 $path = str_replace('\\', '/', $path);
2060
2061 # Add trailing slash
2062 if ( isset($path[strlen($path)-1]) && $path[strlen($path)-1] != '/' ) {
2063 $path .= '/';
2064 }
2065
2066 # Save in session
2067 $_SESSION['logging_destination'] = $path;
2068
2069 # Redirect to avoid "Resend Post?" on refresh
2070 $location->redirect('logs');
2071
2072 }
2073
2074 # Find status
2075 $enabled = empty($CONFIG['enable_logging']) == false;
2076 $status = $enabled ? '<span class="ok-color">enabled</span>' : '<span class="error-color">disabled</span>';
2077 $destination = isset($CONFIG['logging_destination']) ? $CONFIG['logging_destination'] : '';
2078
2079 # Are we overriding the real destination with some other value?
2080 if ( ! empty($_SESSION['logging_destination']) ) {
2081 $destination = $_SESSION['logging_destination'];
2082 }
2083
2084 # Print header
2085 $output->title = 'log viewer';
2086 $output->bodyTitle = 'Logging';
2087
2088 echo <<<OUT
2089 <form action="{$self}?logs" method="post">
2090 <table class="form_table" border="0" cellpadding="0" cellspacing="0">
2091 <tr>
2092 <td align="right">Logging feature:</td>
2093 <td><b>{$status}</b></td>
2094 </tr>
2095 <tr>
2096 <td align="right"><span class="tooltip" onmouseover="tooltip('The value here is for viewing and analysing logs only - changing this has no effect on the proxy logging feature itself and will not change the folder in which new log files are created.')" onmouseout="exit()">Log folder</span>:</td>
2097 <td><input type="text" name="destination" class="inputgri wide-input" value="{$destination}"> <input type="submit" class="button" value="Update »"></td>
2098 </tr>
2099 </table>
2100 </form>
2101 <div class="hr"></div>
2102 <h2>Log files</h2>
2103OUT;
2104
2105 # Do we have any log files to analyze?
2106 if ( ! ( file_exists($destination) && is_dir($destination) && ($logFiles = scandir($destination, 1)) ) ) {
2107
2108 # Print none and exit
2109 echo '<p>No log files to analyze.</p>';
2110 break;
2111
2112 }
2113
2114 # Print starting table
2115 echo <<<OUT
2116 <table class="table" cellpadding="0" cellspacing="0">
2117OUT;
2118
2119 # Set up starting vars
2120 $currentYearMonth = false;
2121 $first = true;
2122 $totalSize = 0;
2123
2124 # Go through all files
2125 foreach ( $logFiles as $file ) {
2126
2127 # Verify files is a glype log. Log files formatted as YYYY-MM-DD.log
2128 if ( ! ( strlen($file) == 14 && preg_match('#^([0-9]{4})-([0-9]{2})-([0-9]{2})\.log$#', $file, $matches) ) ) {
2129 continue;
2130 }
2131
2132 # Extract matches
2133 list(, $yearNumeric, $monthNumeric, $dayNumeric) = $matches;
2134
2135 # Convert filename to timestamp
2136 $timestamp = strtotime(str_replace('.log', ' 12:00 PM', $file));
2137
2138 # Extract time parts
2139 $month = date('F', $timestamp);
2140 $day = date('l', $timestamp);
2141 $display = date('jS', $timestamp) . ' (' . $day . ')';
2142 $yearMonth = $yearNumeric . '-' . $monthNumeric;
2143
2144 # Display in bold if today
2145 if ( $display == date('jS (l)') ) {
2146 $display = '<b>' . $display . '</b>';
2147 }
2148
2149 # Is this a new month?
2150 if ( $yearMonth != $currentYearMonth ) {
2151
2152 # Print in a separate table (unless first)
2153 if ( $first == false ) {
2154 echo <<<OUT
2155 </table>
2156 <br>
2157 <table class="table" cellpadding="0" cellspacing="0">
2158OUT;
2159 }
2160
2161 # Print table header
2162 echo <<<OUT
2163 <tr class="table_header">
2164 <td colspan="2">{$month} {$yearNumeric}</td>
2165 <td>[<a href="{$self}?logs-view&month={$yearMonth}&show=popular-sites">popular sites</a>]</td>
2166 </tr>
2167OUT;
2168
2169 # Update vars so we don't do this again until we want to
2170 $currentYearMonth = $yearMonth;
2171 $first = false;
2172
2173 }
2174
2175 # Format size
2176 $filesize = filesize($destination . $file);
2177 $totalSize += $filesize;
2178
2179 $size = formatSize($filesize);
2180
2181 # Row color is grey if weekend
2182 $row = ( $day == 'Saturday' || $day == 'Sunday' ) ? '3' : '1';
2183
2184 # Print log file row
2185 echo <<<OUT
2186 <tr class="row{$row}">
2187 <td width="150">{$display}</td>
2188 <td width="100">{$size}</td>
2189 <td>
2190 [<a href="{$self}?logs-view&file={$file}&show=raw" target="_blank" title="Opens in a new window">raw log</a>]
2191
2192 [<a href="{$self}?logs-view&file={$file}&show=popular-sites">popular sites</a>]
2193 </td>
2194 </tr>
2195OUT;
2196
2197 }
2198
2199 # End table
2200 $total = formatSize($totalSize);
2201
2202 echo <<<OUT
2203 </table>
2204 <p>Total space used by logs: <b>{$total}</b></p>
2205 <p class="little">Note: Raw logs open in a new window.</p>
2206 <p class="little">Note: You can set up your proxy to automatically delete old logs with the maintenance feature.</p>
2207OUT;
2208
2209 break;
2210
2211
2212 /*****************************************************************
2213 * LOG VIEWER
2214 ******************************************************************/
2215
2216 case 'logs-view':
2217
2218 $output->title = 'view log';
2219 $output->bodyTitle = 'View log file';
2220
2221 # Find log folder
2222 $logFolder = isset($_SESSION['logging_destination']) ? $_SESSION['logging_destination'] : $CONFIG['logging_destination'];
2223
2224 # Verify folder is valid
2225 if ( ! file_exists($logFolder) || ! is_dir($logFolder) ) {
2226
2227 $error->add('The log folder specified does not exist.');
2228 break;
2229
2230 }
2231
2232 # Find file
2233 $file = $input->gFile ? realpath($logFolder . '/' . str_replace('..', '', $input->gFile)) : false;
2234
2235 # What type of viewing do we want?
2236 switch ( $input->gShow ) {
2237
2238 # Raw log file
2239 case 'raw':
2240
2241 # Find file
2242 if ( $file == false || file_exists($file) == false ) {
2243
2244 $error->add('The file specified does not exist.');
2245 break;
2246
2247 }
2248
2249 # Use raw wrapper
2250 $output = new RawOutput;
2251
2252 # And load file
2253 readfile($file);
2254
2255 break;
2256
2257
2258 # Stats - most visited site
2259 case 'popular-sites':
2260
2261 # Scan files to find most popular sites
2262 $scan = array();
2263
2264 # Find files to scan
2265 if ( $file ) {
2266
2267 # Single file mode
2268 $scan[] = $file;
2269
2270 # Date of log file
2271 $date = ( $fileTime = strtotime(basename($input->gFile, '.log')) ) ? date('l jS F, Y', $fileTime) : '[unknown]';
2272
2273 } else if ( $input->gMonth && strlen($input->gMonth) > 5 && ( $logFiles = scandir($logFolder) ) ) {
2274
2275 # Month mode - use all files in given month
2276 foreach ( $logFiles as $file ) {
2277
2278 # Match name
2279 if ( strpos($file, $input->gMonth) === 0 ) {
2280 $scan[] = realpath($logFolder . '/' . $file);
2281 }
2282
2283 }
2284
2285 # Date of log files
2286 $date = date('F Y', strtotime($input->gMonth . '-01'));
2287 }
2288
2289 # Check we have some files to scan
2290 if ( empty($scan) ) {
2291 $error->add('No files to analyze.');
2292 break;
2293 }
2294
2295 # Data array
2296 $visited = array();
2297
2298 # Read through files
2299 foreach ( $scan as $file ) {
2300
2301 # Allow extra time
2302 @set_time_limit(30);
2303
2304 # Open handle to file
2305 if ( ( $handle = fopen($file, 'rb') ) === false ) {
2306 continue;
2307 }
2308
2309 # Scan for URLs
2310 while ( ( $data = fgetcsv($handle, 2000) ) !== false ) {
2311
2312 # Extract URLs
2313 if ( isset($data[2]) && preg_match('#(?:^|\.)([a-z0-9-]+\.(?:[a-z]{2,}|[a-z.]{5,6}))$#i', strtolower(parse_url(trim($data[2]), PHP_URL_HOST)), $tmp) ) {
2314
2315 # Add to tally
2316 if ( isset($visited[$tmp[1]]) ) {
2317
2318 # Increment an existing count
2319 ++$visited[$tmp[1]];
2320
2321 } else {
2322
2323 # Create a new item
2324 $visited[$tmp[1]] = 1;
2325 }
2326 }
2327 }
2328
2329 # Close handle to free resources
2330 fclose($handle);
2331 }
2332
2333 # Sort
2334 arsort($visited);
2335
2336 # Truncate to first X results
2337 $others = array_splice($visited, ADMIN_STATS_LIMIT);
2338
2339 # Sum up the "others" group
2340 $others = array_sum($others);
2341
2342 # Print header
2343 echo <<<OUT
2344 <h2>Most visited sites for {$date}</h2>
2345 <table class="form_table" cellpadding="0" cellspacing="0" width="100%">
2346OUT;
2347
2348 # Find largest value
2349 $max = max($visited);
2350
2351 # Create horizontal bar chart type thing
2352 foreach ( $visited as $site => $count ) {
2353
2354 $rowWidth = round(($count/$max)*100);
2355
2356 # Print it
2357 echo <<<OUT
2358 <tr>
2359 <td width="200" align="right">{$site}</td>
2360 <td><div class="bar" style="width: {$rowWidth}%;">{$count}</div></td>
2361 </tr>
2362OUT;
2363
2364 }
2365
2366 # Table footer
2367 echo <<<OUT
2368 <tr>
2369 <td align="right"><i>Others</i></td>
2370 <td>{$others}</td>
2371 </tr>
2372 </table>
2373 <p class="align-center">« <a href="{$self}?logs">Back</a></p>
2374OUT;
2375
2376 break;
2377
2378 # Anything else - ignore
2379 default:
2380
2381 $error->add('Missing input. No log view specified.');
2382
2383 }
2384
2385 break;
2386
2387
2388 /*****************************************************************
2389 * Everything else - 404
2390 ******************************************************************/
2391
2392 default:
2393
2394 # Send 404 status
2395 $output->sendStatus(404);
2396
2397 # And print the error page
2398 $output->title = 'page not found';
2399 $output->bodyTitle = 'Page Not Found (404)';
2400
2401 echo <<<OUT
2402 <p>The requested page <b>{$_SERVER['REQUEST_URI']}</b> was not found.</p>
2403OUT;
2404
2405}
2406
2407/*****************************************************************
2408* Send content wrapped in our theme
2409******************************************************************/
2410
2411# Get buffer
2412$content = ob_get_contents();
2413
2414# Clear buffer
2415ob_end_clean();
2416
2417# Add content
2418$output->addContent($content);
2419
2420# And print
2421$output->out();