PageRenderTime 39ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/admin.php

https://github.com/francinebo/glype
PHP | 2424 lines | 1302 code | 635 blank | 487 comment | 199 complexity | e0b2925a1667c3fc08c7c52087d990fd MD5 | raw file
  1. <?php
  2. /*******************************************************************
  3. * Glype is copyright and trademark 2007-2012 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. * Configuration - edit this section (if you want!)
  13. ******************************************************************/
  14. # Path to the /includes/settings.php file. Change if you want to move
  15. # this admin script out of the glype directory. You can use a relative
  16. # or absolute path to the file.
  17. define('ADMIN_GLYPE_SETTINGS', 'includes/settings.php');
  18. # How long to keep an inactive admin session open for? After this
  19. # period of inactivity, an admin session is invalidated and you
  20. # must log in again. [seconds]
  21. define('ADMIN_TIMEOUT', 60*60);
  22. # Log viewer limit for collated stats. Limits "most viewed" to
  23. # the top X websites.
  24. define('ADMIN_STATS_LIMIT', 50);
  25. # End of configuration.
  26. /*****************************************************************
  27. * Initialise admin script
  28. ******************************************************************/
  29. # Setup error reporting
  30. error_reporting(E_ALL);
  31. ini_set('display_errors', 1);
  32. # Define a path to us
  33. define('ADMIN_URI', $_SERVER['PHP_SELF']);
  34. # Define the current admin version
  35. define('ADMIN_VERSION', '1.3');
  36. # Start buffering
  37. ob_start();
  38. # Set up equivalents to glype's /includes/init.php constants
  39. # that might be available in the settings file
  40. define('GLYPE_URL', pathToURL(dirname(ADMIN_GLYPE_SETTINGS) . '/..'));
  41. define('GLYPE_ROOT', str_replace('\\', '/', dirname(dirname(realpath(ADMIN_GLYPE_SETTINGS)))));
  42. # And backwards compatability (will be removed at some point)
  43. function findURL() { return GLYPE_URL; }
  44. define('LCNSE_KEY', '');
  45. define('proxyPATH', GLYPE_ROOT . '/');
  46. # Load current settings
  47. $settingsLoaded = file_exists(ADMIN_GLYPE_SETTINGS) && (@include ADMIN_GLYPE_SETTINGS);
  48. # Extract the "action" from the query string
  49. $action = isset($_SERVER['QUERY_STRING']) && preg_match('#^([a-z-]+)#', $_SERVER['QUERY_STRING'], $tmp) ? $tmp[1] : '';
  50. $cache_bust=filemtime(__FILE__)+filemtime(ADMIN_GLYPE_SETTINGS);
  51. # SHORTCUTS
  52. # Make a newline
  53. define('NL', "\r\n");
  54. /*****************************************************************
  55. * IMAGES (ugly but keeps it in a single file)
  56. ******************************************************************/
  57. if ( isset($_GET['image']) ) {
  58. # Send image function
  59. function sendImage($str) {
  60. header('Content-Type: image/gif');
  61. header('Last-Modified: ' . gmdate("D, d M Y H:i:s", filemtime(__FILE__)) . 'GMT');
  62. header('Expires: ' . gmdate("D, d M Y H:i:s", filemtime(__FILE__) + (60*60)) . 'GMT');
  63. echo base64_decode($str);
  64. exit;
  65. }
  66. switch ( $_GET['image'] ) {
  67. case 'bg.gif':
  68. sendImage('R0lGODlhkAMMAMQAAP////7+/v3+/fz9/Pr7+vn6+fj6+Pj5+PX39fL08u/x7+ru6ubq5uDm4OHm4dzi3Njf2NTc1NHZ0c7XzsnTycfRxwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAACQAwwAAAX/4CACZGmeaKoC4oEkysI0DxRJU1WsfO//wKBwSCwaj8ikcslsOp/QqHRKrVqv2KxWWKBIIpCHg7FQJBAGwwgrWldbr9isdsvttvi8fs/v+/+AgYKDhIWGSV1fYWNlZ2luVm0DV3AwMjQ2ODqHnJ2en6ChoqOkpaaAiWBiZGZoapNsbZQDLpZzmXanuru8vb6/wMHCo6mLrI6vWZKztXKYdZvD0tPU1dbX2NmCxauNrpBvspG0cZd0mnfa6uvs7e7v8KTcjK2PsLPgUpXO57nx/wADChxIsGCJece+3RuXL8o+c7iiGZxIsaLFixj/IPRmT5m4cM0gQkuXsaTJkyhTO6bcWC9ZrIZQHt4aqbKmzZs4c1ZjiQymw49UZD5Dp7Oo0aNIk/bhqdCjTydC+0lUSrWq1atYVTDtyCYEADs=');
  69. break;
  70. case 'bullet_green.gif':
  71. sendImage('R0lGODlhEAAQAMQAAFOYS////6LUoJW4kI/Bi+nv6F28V5TSlLTcs5TMkYTKgp3VmlelUJvKluv26r/hv4zOhJ/cnJnMmV7CWev461WcTaPUoZa6kZrWlr3etYrMiQAAAAAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAAAQABAAAAUzYCCOZGmeaKqu7ElF07Q4aoQ9TyKp03NoFobKgNAoJBXVIiGQEC4qR6MCGBRa2Kx2qwoBADs=');
  72. break;
  73. case 'bullet_grey.gif':
  74. sendImage('R0lGODlhEAAQALMAAHR0dLW1te/v76Wlpby8vI2NjcfHx62trXp6eszMzP///wAAAAAAAAAAAAAAAAAAACH5BAEHAAoALAAAAAAQABAAAAQtUMlJq704682vIEURCBoRJMkRaEUSDATCGscQAFpwmMOgCQcAYEDqGI/IZCYCADs=');
  75. break;
  76. case 'button.gif':
  77. sendImage('R0lGODlhCgAoAKIAAOno6Ovq6/f39////+vp6vPx8uzr6u/v7yH5BAAHAP8ALAAAAAAKACgAAAMtOLos/jDKSWssOOvNOz5gKI6jYZ5oqq4m4b4EIM90bd94ru987//AoHBILBoTADs=');
  78. break;
  79. case 'content_bg.gif':
  80. sendImage('R0lGODlhCAAyALMAAP////v9/fj7+/X6+vH4+O729ur09Ofy8uPw8N/v79zt7djr6wAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAAAIADIAAAQ8EMgpl7046827/5oijmJiniairurhvq4hz3Jh3zah7/rg/z6BcCgMGI9GinLJbDqf0Kh0Sq1ar9islhoBADs=');
  81. break;
  82. case 'footer.gif':
  83. 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');
  84. break;
  85. case 'footer_bg.gif':
  86. sendImage('R0lGODlhMgAyAMQAAIXDKYTCKYPAKIPBKIK/KIG+KIG9KIC8J4C7J3+6J3+5J364Jn23Jn22Jny1JXu0JXqzJXmyJHmxJHevJHiwJHauIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAAAyADIAAAX/ICCOZGmeaKqubOu+cCzPdG3feK7vfO//wKCwFCgaj8ikcslsOp9Q5mBKrVqv2KzWKuh6v+CweEwum89oMmHNbrvf8Lh8Tq/b5YW8fs/v+/+AgYKDhIAGh4iJiouMjY6KB5GSk5SVlpeYmZqbnJgIn6ChoqOkpaanqKmqpgmtrq+wsbKztLAKt7i5uru8vb6/wMHCvgvFxsfIycrLzM3Oz9DMDNPU1dbX2Nna29zd3toN4eLj5OXm5+jkDuvs7e7v8PHy8/T19vIP+fr7/P3+/wADChxIsKDBgwUhKFzIsKHDhxAjSpxIsWLECBgzatzIsaPHjxsliBxJsqTJkyhTNKpcybJlSgowY8qcSbOmzZs4c+rceXOCz59AgwodSrSo0aNIkxatwLSp06dQo0qd+nTIixAAOw==');
  87. break;
  88. case 'nane.gif':
  89. sendImage('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');
  90. break;
  91. case 'tableheader-bg-grey.gif':
  92. 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');
  93. break;
  94. case 'tableheader-bg.gif':
  95. 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==');
  96. break;
  97. case 'top_left.gif':
  98. sendImage('R0lGODlhCgAyAMQAAKvWa5rNTYjFL4XDKYTCKYTBKIPBKIPAKIK/KIG+J4G+KIC9J4C8J3+7J366Jn66J365Jn24Jny2Jnu2Jny3Jnu1JXq0JQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAHAP8ALAAAAAAKADIAAAV/ICCOZFkGaKoKbOsScCwbdG0jeK4nfO8zwKCwQSwaH8ikMsJsOinQqHRCrVot2Kx2u614v+BwWEIum89nqXqtdrrf8DhkTq877vi8cc/fC/+AfwuDhIWGhgqJiouMjDqPkJGSB5SVlpeXBZqbnJ2dMqChoAOkpaanqKmqq6ynIQA7');
  99. break;
  100. case 'loading.gif':
  101. sendImage('R0lGODlhEAAQAPIAAP///2ZmZtra2o2NjWZmZqCgoLOzs729vSH+GkNyZWF0ZWQgd2l0aCBhamF4bG9hZC5pbmZvACH5BAAKAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQACgABACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkEAAoAAgAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkEAAoAAwAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkEAAoABAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQACgAFACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQACgAGACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAAKAAcALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==');
  102. break;
  103. }
  104. }
  105. /*****************************************************************
  106. * FUNCTIONS
  107. ******************************************************************/
  108. # Quote string
  109. function quote($str) {
  110. $str = str_replace('\\', '\\\\', $str);
  111. $str = str_replace("'", "\'", $str);
  112. return "'$str'";
  113. }
  114. # Convert filesize from bytes to most suitable unit
  115. function formatSize($bytes) {
  116. # Define suitable units in 1024x increments
  117. $types = array( 'B', 'KB', 'MB', 'GB', 'TB' );
  118. # Decrease until we run out of units or we're less than 1024 in the current unit
  119. for ( $i = 0, $l = count($types)-1; $bytes >= 1024 && $i < $l; $bytes /= 1024, $i++ );
  120. # Return a rounded figure with unit
  121. return ( round($bytes, 2) . ' ' . $types[$i] );
  122. }
  123. # Convert path to URL
  124. function pathToURL($filePath) {
  125. # Run through realpath to normalise path
  126. $realPath = realpath($filePath);
  127. # Verify that the path passed is real and find the directory
  128. if ( is_file($realPath)) {
  129. $dir = dirname($realPath);
  130. } elseif ( is_dir($realPath) ) {
  131. $dir = $realPath;
  132. } else {
  133. # Path does not exist, fails
  134. return false;
  135. }
  136. # Expand the document root path
  137. $_SERVER['DOCUMENT_ROOT'] = realpath($_SERVER['DOCUMENT_ROOT']);
  138. # Make sure the path is not lower than the server root
  139. if ( strlen($dir) < strlen($_SERVER['DOCUMENT_ROOT']) ) {
  140. return false;
  141. }
  142. # Determine path from web root
  143. $rootPos = strlen($_SERVER['DOCUMENT_ROOT']);
  144. # Make sure $rootPos includes the first slash
  145. if ( ( $tmp = substr($_SERVER['DOCUMENT_ROOT'], -1) ) && ( $tmp == '/' || $tmp == '\\' ) ) {
  146. --$rootPos;
  147. }
  148. # Extract path below webroot and discard path above webroot
  149. $pathFromRoot = substr($realPath, $rootPos);
  150. # Build URL from parts
  151. $path = 'http' . ( isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off' ? 's' : '' ) . '://' . $_SERVER['HTTP_HOST'] . $pathFromRoot;
  152. # Convert to forward slash if on Windows
  153. if ( DIRECTORY_SEPARATOR == '\\' ) {
  154. $path = str_replace('\\', '/', $path);
  155. }
  156. return $path;
  157. }
  158. # Hide from non-js browsers by using document.write() to output
  159. function jsWrite($str) {
  160. return '<script type="text/javascript">document.write(' . quote($str) . ');</script>';
  161. }
  162. # Convert a string of bool value to bool
  163. function bool($str) {
  164. if ( $str == 'false' ) {
  165. return false;
  166. }
  167. if ( $str == 'true' ) {
  168. return true;
  169. }
  170. return NULL;
  171. }
  172. /*****************************************************************
  173. * CLASSES
  174. ******************************************************************/
  175. /*****************************************************************
  176. * Location wrapper - allows us to have observers on the location
  177. ******************************************************************/
  178. class Location {
  179. # Observers
  180. private $observers;
  181. # Redirect elsewhere
  182. public function redirect($to = '') {
  183. # Notify observers
  184. $this->notifyObservers('redirect');
  185. # Redirect and quit
  186. header('Location: ' . ADMIN_URI . '?' . $to);
  187. exit;
  188. }
  189. # Redirect elsewhere but without observer
  190. public function cleanRedirect($to = '') {
  191. # Redirect and quit
  192. header('Location: ' . ADMIN_URI . '?' . $to);
  193. exit;
  194. }
  195. # Register observers
  196. public function addObserver(&$obj) {
  197. $this->observers[] = $obj;
  198. }
  199. # Notify observers
  200. public function notifyObservers($action) {
  201. # Determine method to call
  202. $method = 'on' . ucfirst($action);
  203. # Prepare parameters
  204. $params = func_get_args();
  205. array_shift($params);
  206. # Loop through all observers
  207. foreach ( $this->observers as $obj ) {
  208. # If an observing method exists, call it
  209. if ( method_exists($obj, $method) ) {
  210. call_user_func_array(array(&$obj, $method), $params);
  211. }
  212. }
  213. }
  214. }
  215. /*****************************************************************
  216. * Input wrapper for incoming data
  217. ******************************************************************/
  218. class Input {
  219. # Set up inputs
  220. public function __construct() {
  221. $this->GET = $this->prepare($_GET);
  222. $this->POST = $this->prepare($_POST);
  223. $this->COOKIE = $this->prepare($_COOKIE);
  224. }
  225. # Return array with keys converted to lowercase and values cleaned
  226. private function prepare($array) {
  227. $return = array();
  228. foreach ( $array as $key => $value ) {
  229. $return[strtolower($key)] = self::clean($value);
  230. }
  231. return $return;
  232. }
  233. # Get an input - inputs can be requested in the form pVarName
  234. # where VarName is (case insensitive) name of variable (duh!)
  235. # and p denotes from _POST. G and C are also available.
  236. public function __get($name) {
  237. # Do we have a varname?
  238. if ( ! isset($name[1]) ) {
  239. return NULL;
  240. }
  241. # Split into GPC and VarName (case insensitive)
  242. $from = strtolower($name[0]);
  243. $var = strtolower(substr($name, 1));
  244. # Define $from to target relationships
  245. $targets = array('g' => $this->GET,
  246. 'p' => $this->POST,
  247. 'c' => $this->COOKIE);
  248. # Look for the value and return it
  249. if ( isset($targets[$from][$var]) ) {
  250. return $targets[$from][$var];
  251. }
  252. # Not found, return false
  253. return NULL;
  254. }
  255. # Clean a value
  256. static public function clean($val) {
  257. static $magicQuotes;
  258. # What is our magic quotes setting?
  259. if ( ! isset($magicQuotes) ) {
  260. $magicQuotes = get_magic_quotes_gpc();
  261. }
  262. # What type is this?
  263. switch ( true ) {
  264. case is_string($val):
  265. # Strip slashes and trim
  266. if ( $magicQuotes ) {
  267. $val = stripslashes($val);
  268. }
  269. $val = trim($val);
  270. break;
  271. case is_array($val):
  272. $val = array_map(array('Input', 'clean'), $val);
  273. break;
  274. default:
  275. return $val;
  276. }
  277. return $val;
  278. }
  279. }
  280. /*****************************************************************
  281. * Output wrappers
  282. ******************************************************************/
  283. # A simple overloading object
  284. class Overloader {
  285. # Store variables in this array
  286. protected $data;
  287. # Set value (case insensitive)
  288. public function __set($name, $value) {
  289. $this->data[strtolower($name)] = $value;
  290. }
  291. # Get value (case insensitive)
  292. public function __get($name) {
  293. $name = strtolower($name);
  294. return isset($this->data[$name]) ? $this->data[$name] : '';
  295. }
  296. }
  297. # Base wrapper object
  298. abstract class Output extends Overloader {
  299. # Full page to output
  300. protected $output;
  301. # Content only
  302. protected $content;
  303. # Array of observers
  304. protected $observers = array();
  305. # Output the page
  306. final public function out() {
  307. # Notify our observers we're about to print
  308. $this->notifyObservers('print', $this);
  309. # Wrap content in our wrapper
  310. $this->wrap();
  311. # Send headers
  312. $this->sendHeaders();
  313. # Send body
  314. print $this->output;
  315. # Page completed, finish
  316. exit;
  317. }
  318. # Override this to send custom headers instead of default (html)
  319. protected function sendHeaders() {}
  320. # Wrapper for body content
  321. protected function wrap() {
  322. $this->output = $this->content;
  323. }
  324. # Add content
  325. public function addContent($content) {
  326. $this->content .= $content;
  327. }
  328. # Register observers
  329. public function addObserver(&$obj) {
  330. $this->observers[] = $obj;
  331. }
  332. # Notify observers
  333. public function notifyObservers($action) {
  334. # Determine method to call
  335. $method = 'on' . ucfirst($action);
  336. # Prepare parameters
  337. $params = func_get_args();
  338. array_shift($params);
  339. # Loop through all observers
  340. foreach ( $this->observers as $obj ) {
  341. # If an observing method exists, call it
  342. if ( method_exists($obj, $method) ) {
  343. call_user_func_array(array(&$obj, $method), $params);
  344. }
  345. }
  346. }
  347. # Send status code
  348. public function sendStatus($code) {
  349. header(' ', true, $code);
  350. }
  351. # More overloading. Set value with key.
  352. public function __call($func, $args) {
  353. if ( substr($func, 0, 3) == 'add' && strlen($func) > 3 && ! isset($args[2]) ) {
  354. # Saving with key or not?
  355. if ( isset($args[1]) ) {
  356. $this->data[strtolower(substr($func, 3))][$args[0]] = $args[1];
  357. } else {
  358. $this->data[strtolower(substr($func, 3))][] = $args[0];
  359. }
  360. }
  361. }
  362. }
  363. # Output with our HTML skin
  364. class SkinOutput extends Output {
  365. # Print all
  366. private function printAll($name) {
  367. $name = strtolower($name);
  368. if ( isset($this->data[$name]) && is_array($this->data[$name]) ) {
  369. foreach ( $this->data[$name] as $item ) {
  370. echo $item;
  371. }
  372. }
  373. }
  374. # Wrap content in HTML skin
  375. protected function wrap() {
  376. # Prepare the "get image" path
  377. $imgs = ADMIN_URI . '?image=';
  378. # Self
  379. $self = ADMIN_URI;
  380. # Prepare date
  381. $date = date('H:i, d F Y');
  382. # Append "glype control panel" to title
  383. $title = $this->title . ( $this->title ? ' : ' : '' ) . 'Glype control panel';
  384. # Buffer so we can get this into a variable
  385. ob_start();
  386. # Print output
  387. echo <<<OUT
  388. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  389. <html xmlns="http://www.w3.org/1999/xhtml" >
  390. <head>
  391. <title>{$title}</title>
  392. <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='&nbsp;';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>
  393. <script type="text/javascript">
  394. OUT;
  395. # Add domReady javascript
  396. if ( $this->domReady ) {
  397. echo 'window.addDomReadyFunc(function(){', $this->printAll('domReady'), '});';
  398. }
  399. # Add other javascript
  400. if ( $this->javascript ) {
  401. echo $this->printAll('javascript');
  402. }
  403. echo <<<OUT
  404. </script>
  405. <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>
  406. </head>
  407. <body>
  408. <div id="wrap">
  409. <div id="top_content">
  410. <div id="header">
  411. <div id="rightheader">
  412. <p>
  413. {$date}
  414. <br />
  415. OUT;
  416. # Add the "welcome" and log out link
  417. if ( $this->admin ) {
  418. echo "welcome, <i>{$this->admin}</i> : <strong><a href=\"{$self}?logout\">log out</a></strong>\r\n";
  419. }
  420. $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
  421. echo <<<OUT
  422. </p>
  423. </div>
  424. <div id="topheader">
  425. <h1 id="title">
  426. <a href="{$self}">Glype Admin Control Panel</a><br>
  427. <span>for {$http_host}</span>
  428. </h1>
  429. </div>
  430. <div id="navigation">
  431. <ul>
  432. OUT;
  433. # Add navigation
  434. if ( is_array($this->navigation) ) {
  435. foreach ( $this->navigation as $text => $href ) {
  436. if (stripos($href,$self)!==false) {
  437. echo "<li><a href=\"{$href}\">{$text}</a></li>\r\n";
  438. } else {
  439. echo "<li><a href=\"{$href}\" target=\"_blank\">{$text}</a></li>\r\n";
  440. }
  441. }
  442. }
  443. echo <<<OUT
  444. </ul>
  445. </div>
  446. </div>
  447. <div id="content">
  448. <h1>{$this->bodyTitle}</h1>
  449. OUT;
  450. # Do we have any error messages?
  451. if ( $this->error ) {
  452. # Print all
  453. foreach ( $this->error as $id => $message ) {
  454. echo <<<OUT
  455. <div class="notice_error" id="notice_error_{$id}">
  456. <a class="close" title="Dismiss" onclick="document.getElementById('notice_error_{$id}').style.display='none';">X</a>
  457. {$message}
  458. </div>
  459. OUT;
  460. }
  461. }
  462. # Do we have any confirmation messages?
  463. if ( $this->confirm ) {
  464. # Print all
  465. foreach ( $this->confirm as $id => $message ) {
  466. echo <<<OUT
  467. <div class="notice" id="notice_{$id}">
  468. <a class="close" title="Dismiss" onclick="document.getElementById('notice_{$id}').style.display='none';">X</a>
  469. {$message}
  470. </div>
  471. OUT;
  472. }
  473. }
  474. # Print content
  475. echo $this->content;
  476. # Print footer links
  477. if ( is_array($this->footerLinks) ) {
  478. echo '
  479. <br>
  480. <div class="other_links">
  481. <h2>See also</h2>
  482. <ul class="other">
  483. ';
  484. foreach ( $this->footerLinks as $text => $href ) {
  485. echo "<li><a href=\"{$href}\">{$text}</a></li>\r\n";
  486. }
  487. echo '
  488. </ul>
  489. </div>
  490. ';
  491. }
  492. # And finish off the page
  493. echo <<<OUT
  494. </div>
  495. </div>
  496. <div id="footer">
  497. <div id="footer_bg">
  498. <p><a href="http://www.glype.com/">Glype</a>&reg; &copy; 2007-2012 Glype. All rights reserved.</p>
  499. </div>
  500. </div>
  501. </div>
  502. <div id="preload">
  503. <span class="ajax-loading">&nbsp;</span>
  504. </div>
  505. </body>
  506. </html>
  507. OUT;
  508. $this->output = ob_get_contents();
  509. # Discard buffer
  510. ob_end_clean();
  511. }
  512. }
  513. # Send output in "raw" form
  514. class RawOutput extends Output {
  515. protected function sendHeaders() {
  516. header('Content-Type: text/plain; charset="utf-8"');
  517. header('Content-Disposition: inline; filename=""');
  518. }
  519. }
  520. /*****************************************************************
  521. * User object
  522. ******************************************************************/
  523. # Manage sessions and stores user data
  524. class User {
  525. # Username we're logged in as
  526. public $name;
  527. # Our user agent
  528. public $userAgent;
  529. # Our IP address
  530. public $IP;
  531. # Reason for aborting a session
  532. public $aborted;
  533. # Constructor sets up session
  534. public function __construct() {
  535. # Don't try to start if autostarted
  536. if ( session_id() == '' ) {
  537. # Set up new session
  538. session_name('admin');
  539. session_start();
  540. }
  541. # Always use a fresh ID for security
  542. session_regenerate_id();
  543. # Prepare user data
  544. $this->userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
  545. $this->IP = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
  546. # Use user-agent and IP as identifying data since these shouldn't change mid-session
  547. $authKey = $this->userAgent . $this->IP;
  548. # Do we have a stored auth key?
  549. if ( isset($_SESSION['auth_key']) ) {
  550. # Compare our current auth_key to stored key
  551. if ( $_SESSION['auth_key'] != $authKey ) {
  552. # Mismatch. Session may be stolen.
  553. $this->clear();
  554. $this->aborted = 'Session data mismatch.';
  555. }
  556. } else {
  557. # No stored auth key, save it
  558. $_SESSION['auth_key'] = $authKey;
  559. }
  560. # Are we verified?
  561. if ( ! empty($_SESSION['verified']) ) {
  562. $this->name = $_SESSION['verified'];
  563. }
  564. # Have we expired? Only expire if we're logged in of course...
  565. if ( $this->isAdmin() && isset($_SESSION['last_click']) && $_SESSION['last_click'] < (time() - ADMIN_TIMEOUT) ) {
  566. $this->clear();
  567. $this->aborted = 'Your session timed out after ' . round(ADMIN_TIMEOUT/60) . ' minutes of inactivity.';
  568. }
  569. # Set last click time
  570. $_SESSION['last_click'] = time();
  571. }
  572. # Log out, destroy all session data
  573. public function clear() {
  574. # Clear existing
  575. session_destroy();
  576. # Unset existing variables
  577. $_SESSION = array();
  578. $this->name = false;
  579. # Restart session
  580. session_start();
  581. }
  582. # Log in, saving username session for future requests
  583. public function login($name) {
  584. $this->name = $name;
  585. $_SESSION['verified'] = $name;
  586. }
  587. # Are we verified or not?
  588. public function isAdmin() {
  589. return (bool) $this->name;
  590. }
  591. }
  592. /*****************************************************************
  593. * Notice handler (errors or confirmations)
  594. ******************************************************************/
  595. class Notice {
  596. # Storage of messages
  597. private $data = array();
  598. # Type of notice handler
  599. private $type;
  600. # Constructor fetches any stored from session and clears session
  601. public function __construct($type) {
  602. # Save type
  603. $this->type = $type;
  604. # Array key
  605. $key = 'notice_' . $type;
  606. # Any existing?
  607. if ( isset($_SESSION[$key]) ) {
  608. # Extract
  609. $this->data = $_SESSION[$key];
  610. # And clear
  611. unset($_SESSION[$key]);
  612. }
  613. }
  614. # Get messages
  615. public function get($id = false) {
  616. # Requesting an individual message?
  617. if ( $id !== false ) {
  618. return isset($this->data[$id]) ? $this->data[$id] : false;
  619. }
  620. # Requesting all
  621. return $this->data;
  622. }
  623. # Add message
  624. public function add($msg, $id = false) {
  625. # Add with or without an explicit key
  626. if ( $id ) {
  627. $this->data[$id] = $msg;
  628. } else {
  629. $this->data[] = $msg;
  630. }
  631. }
  632. # Do we have any messages?
  633. public function hasMsg() {
  634. return ! empty($this->data);
  635. }
  636. # Observer the print method of output
  637. public function onPrint($output) {
  638. $funcName = 'add' . $this->type;
  639. # Add our messages to the output object
  640. foreach ( $this->data as $msg ) {
  641. $output->{$funcName}($msg);
  642. }
  643. }
  644. # Observe redirects - store notices in session
  645. public function onRedirect() {
  646. $_SESSION['notice_' . $this->type] = $this->data;
  647. }
  648. }
  649. /*****************************************************************
  650. * Initialise instances of defined classes. If we were structing
  651. * this nicely we'd stick the above in separate files to keep it
  652. * clean but we're sacrificing good structure for the convenience
  653. * of running this admin script stand-alone.
  654. ******************************************************************/
  655. # Create output object
  656. $output = new SkinOutput;
  657. # Create an overloader object to hold our template vars.
  658. # This keeps them all together and avoids problems with undefined variable notices.
  659. $tpl = new Overloader;
  660. # Location wrapper for redirections
  661. $location = new Location;
  662. # Create user object
  663. $user = new User();
  664. # Create notice handlers
  665. $confirm = new Notice('confirm');
  666. $error = new Notice('error');
  667. # Input wrapper
  668. $input = new Input;
  669. /*****************************************************************
  670. * Nearly finished preparing, now just bind them together as appropriate
  671. ******************************************************************/
  672. # Add notice handlers as observers of the output object
  673. $output->addObserver($confirm);
  674. $output->addObserver($error);
  675. # Add notice handlers as observers on redirect();
  676. $location->addObserver($confirm);
  677. $location->addObserver($error);
  678. # Pass user details to output object
  679. $output->admin = $user->name;
  680. /*****************************************************************
  681. * AJAX INTERCEPTS
  682. ******************************************************************/
  683. if ( $input->gFetch && $user->isAdmin() ) {
  684. # Stop caching of response
  685. header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
  686. header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT' );
  687. header('Cache-Control: no-cache, must-revalidate');
  688. header('Pragma: no-cache');
  689. switch ( $input->gFetch ) {
  690. # Get the latest news
  691. case 'news':
  692. # Style the news
  693. echo '<style type="text/css">body { margin:0; padding:5px; font:80% Tahoma,Verdana; } a { color: #73A822; }</style>';
  694. # Connect to glype
  695. if ($ch=curl_init('http://www.glype.com/feeds/news.php?vn='.urlencode($CONFIG['version']).'&lk='.urlencode($CONFIG['license_key']).'&cb='.$cache_bust)) {
  696. curl_setopt($ch, CURLOPT_TIMEOUT, 2);
  697. $success = curl_exec($ch);
  698. curl_close($ch);
  699. }
  700. # Ensure we have a return
  701. if ( empty($success) ) {
  702. echo 'Currently unable to connect to glype.com for a news update.';
  703. }
  704. break;
  705. # Verify a directory exists and is writable
  706. case 'test-dir':
  707. $fail = false;
  708. # Verify
  709. if ( ! ( $dir = $input->gDir ) ) {
  710. # Check we have a dir to test
  711. $fail = 'no directory given';
  712. } else if ( ! file_exists($dir) || ! is_dir($dir) ) {
  713. # Check it exists and is actually a directory
  714. $fail = 'directory does not exist';
  715. # Try to create it (in case it was inside the temporary directory)
  716. if ( ! bool($input->gTmp) && is_writable(dirname($dir)) && @mkdir($dir, 0755, true) ) {
  717. # Reset error messages and delete directory
  718. $fail = false;
  719. $ok = 'directory does not exist but can be created';
  720. rmdir($dir);
  721. }
  722. } else if ( ! is_writable($dir) ) {
  723. # Make sure it's writable
  724. $fail = 'directory not writable - permission denied';
  725. } else {
  726. # OK
  727. $ok = 'directory exists and is writable';
  728. }
  729. # Print result
  730. if ( $fail ) {
  731. echo '<span class="error-color">Error:</span> ', $fail;
  732. } else {
  733. echo '<span class="ok-color">OK:</span> ', $ok;
  734. }
  735. break;
  736. }
  737. # Finish here
  738. exit;
  739. }
  740. /*****************************************************************
  741. * Did our settings file load? If not, nothing else we can do.
  742. ******************************************************************/
  743. if ( ! $settingsLoaded ) {
  744. # Show error and exit
  745. $error->add('The settings file for Glype could not be found.
  746. Please upload this tool into your root glype directory.
  747. If you wish to run this script from another location,
  748. edit the configuration options at the top of the file.
  749. <br><br>
  750. Attempted to load: <b>' . ADMIN_GLYPE_SETTINGS . '</b>');
  751. $output->out();
  752. }
  753. /*****************************************************************
  754. * Verify a valid action and force to something else if not.
  755. ******************************************************************/
  756. # Are we an admin? If not, force login page.
  757. if ( ! $user->isAdmin() ) {
  758. $action = 'login';
  759. }
  760. # Do we even have any user details? If not, force installer.
  761. if ( ! isset($adminDetails) ) {
  762. $action = 'install';
  763. }
  764. /*****************************************************************
  765. * Prepare template variables
  766. ******************************************************************/
  767. # URI to self
  768. $self = ADMIN_URI;
  769. # Links to other sections of the control panel
  770. if ( $user->isAdmin() ) {
  771. $output->addNavigation('Home', $self);
  772. $output->addNavigation('Edit Settings', $self.'?settings');
  773. $output->addNavigation('View Logs', $self.'?logs');
  774. $output->addNavigation('Glype&reg; Licenses', 'https://www.glype.com/purchase.php');
  775. $output->addNavigation('BlockScript&reg;', $self.'?blockscript');
  776. $output->addNavigation('Support Forum', 'http://proxy.org/forum/glype-proxy/');
  777. $output->addNavigation('Promote Your Proxy', 'https://proxy.org/advertise.shtml');
  778. }
  779. /*****************************************************************
  780. * Process current request.
  781. ******************************************************************/
  782. switch ( $action ) {
  783. /*****************************************************************
  784. * INSTALL - save an admin username/password in our settings file
  785. ******************************************************************/
  786. case 'install':
  787. # Do we have any admin details already?
  788. if ( isset($adminDetails) ) {
  789. # Add error
  790. $error->add('An administrator account already exists. For security reasons, you must manually create additional administrator accounts.');
  791. # And redirect to index
  792. $location->redirect();
  793. }
  794. # Do we have any submitted details to process?
  795. if ( $input->pSubmit ) {
  796. # Verify inputs
  797. if ( ! ( $username = $input->pAdminUsername ) ) {
  798. $error->add('You must enter a username to protect access to your control panel!');
  799. }
  800. if ( ! ( $password = $input->pAdminPassword ) ) {
  801. $error->add('You must enter a password to protect access to your control panel!');
  802. }
  803. # In case things go wrong, add this into the template
  804. $tpl->username = $username;
  805. # Process the installation if no errors
  806. if ( ! $error->hasMsg() && is_writable(ADMIN_GLYPE_SETTINGS) ) {
  807. # Load up the file
  808. $file = file_get_contents(ADMIN_GLYPE_SETTINGS);
  809. # Clear any closing php tag ? > (unnecessary and gets in the way)
  810. if ( substr(trim($file), -2) == '?>' ) {
  811. $file = substr(trim($file), 0, -2);
  812. }
  813. # Look for a "Preserve Me" section
  814. if ( strpos($file, '//---PRESERVE ME---') === false ) {
  815. # If it doesn't exist, add it
  816. $file .= "\r\n//---PRESERVE ME---
  817. # Anything below this line will be preserved when the admin control panel rewrites
  818. # the settings. Useful for storing settings that don't/can't be changed from the control panel\r\n";
  819. }
  820. # Prepare the inputs
  821. $password = md5($password);
  822. # Add to file
  823. $file .= "\r\n\$adminDetails[" . quote($username) . "] = " . quote($password) . ";\r\n";
  824. # Save updated file
  825. if ( file_put_contents(ADMIN_GLYPE_SETTINGS, $file) ) {
  826. # Add confirmation
  827. $confirm->add('Installation successful. You have added <b>' . $username . '</b> as an administrator and are now logged in.');
  828. # Log in the installer
  829. $user->login($username);
  830. } else {
  831. # Add error message
  832. $error->add('Installation failed. The settings file appears writable but file_put_contents() failed.');
  833. }
  834. # Redirect
  835. $location->redirect();
  836. }
  837. }
  838. # Prepare skin variables
  839. $output->title = 'install';
  840. $output->bodyTitle = 'First time use installation';
  841. # Add javascript
  842. $output->addDomReady("document.getElementById('username').focus();");
  843. # Is the settings file writable?
  844. if ( ! ( $writable = is_writable(ADMIN_GLYPE_SETTINGS) ) ) {
  845. $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.');
  846. # And disable the submit button
  847. $tpl->disabled = ' disabled="disabled"';
  848. } else {
  849. $confirm->add('Settings file was found and is writable. Installation can proceed. <b>Do not leave the script at this stage!</b>');
  850. }
  851. # Print form
  852. echo <<<OUT
  853. <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>
  854. <form action="{$self}?install" method="post">
  855. <table class="form_table" border="0" cellpadding="0" cellspacing="0">
  856. <tr>
  857. <td align="right">Username:</td>
  858. <td align="left"><input class="inputgri" id="username" name="adminUsername" type="text" value="{$tpl->username}"></td>
  859. </tr>
  860. <tr>
  861. <td align="right">Password:</td>
  862. <td align="left"><input class="inputgri" name="adminPassword" type="password"></td>
  863. </tr>
  864. </table>
  865. <p><input class="button" value="Submit &raquo;" name="submit" type="submit"{$tpl->disabled}></p>
  866. </form>
  867. OUT;
  868. break;
  869. /*****************************************************************
  870. * LOG IN
  871. ******************************************************************/
  872. case 'login':
  873. # Do we have any login details to process?
  874. if ( $input->pLoginSubmit ) {
  875. # Verify inputs
  876. if ( ! ( $username = $input->pAdminUsername ) ) {
  877. $error->add('You did not enter your username. Please try again.');
  878. }
  879. if ( ! ( $password = $input->pAdminPassword ) ) {
  880. $error->add('You did not enter your password. Please try again.');
  881. }
  882. # Validate the submitted details
  883. if ( ! $error->hasMsg() ) {
  884. # Validate submitted password
  885. if ( isset($adminDetails[$username]) && $adminDetails[$username] == md5($password) ) {
  886. # Update user
  887. $user->login($username);
  888. # Redirect to index
  889. $location->cleanRedirect();
  890. } else {
  891. # Incorrect password
  892. $error->add('The login details you submitted were incorrect.');
  893. }
  894. }
  895. }
  896. # Have we been automatically logged out?
  897. if ( $user->aborted ) {
  898. $error->add($user->aborted);
  899. }
  900. # Set up page titles
  901. $output->title = 'log in';
  902. $output->bodyTitle = 'Log in';
  903. # Add javascript
  904. $output->addDomReady("document.getElementById('username').focus();");
  905. # Show form
  906. echo <<<OUT
  907. <p>This is a restricted area for authorised users only. Enter your log in details below.</p>
  908. <form action="{$self}?login" method="post">
  909. <table class="form_table" border="0" cellpadding="0" cellspacing="0">
  910. <tr>
  911. <td align="right">Username:</td>
  912. <td align="left"><input class="inputgri" id="username" name="adminUsername" type="text"></td>
  913. </tr>
  914. <tr>
  915. <td align="right">Password:</td>
  916. <td align="left"><input class="inputgri" name="adminPassword" type="password"></td>
  917. </tr>
  918. </table>
  919. <p><input class="button" value="Submit &raquo;" name="loginsubmit" type="submit"></p>
  920. </form>
  921. OUT;
  922. break;
  923. /*****************************************************************
  924. * LOG OUT
  925. ******************************************************************/
  926. case 'logout':
  927. # Clear all user data
  928. $user->clear();
  929. # Print confirmation
  930. $confirm->add('You are now logged out.');
  931. # Redirect back to login page
  932. $location->redirect('login');
  933. break;
  934. /*****************************************************************
  935. * INDEX - check status and print summary
  936. ******************************************************************/
  937. case '':
  938. #
  939. # System requirements
  940. #
  941. $requirements = array();
  942. # PHP VERSION ----------------------
  943. # Find PHP version - may be bundled OS so strip that out
  944. $phpVersion = ( $tmp = strpos(PHP_VERSION, '-') ) ? substr(PHP_VERSION, 0, $tmp) : PHP_VERSION;
  945. # Check above 5 and if not, add error text
  946. if ( ! ( $ok = version_compare($phpVersion, '5', '>=') ) ) {
  947. $error->add('Glype requires at least PHP 5 or greater.');
  948. }
  949. # Add to requirements
  950. $requirements[] = array(
  951. 'name' => 'PHP version',
  952. 'value' => $phpVersion,
  953. 'ok' => $ok
  954. );
  955. # CURL -------------------------------
  956. # Check for libcurl
  957. if ( ! ( $ok = function_exists('curl_version') ) ) {
  958. $error->add('Glype requires cURL/libcurl.');
  959. }
  960. # curl version
  961. $curlVersion = $ok && ( $tmp = curl_version() ) ? $tmp['version'] : 'not available';
  962. # Add to requirements
  963. $requirements[] = array(
  964. 'name' => 'cURL version',
  965. 'value' => $curlVersion,
  966. 'ok' => $ok
  967. );
  968. # --------------------------------------
  969. # Print page header
  970. $output->bodyTitle = 'Welcome to your control panel';
  971. #
  972. # Glype news
  973. #
  974. echo <<<OUT
  975. <p>This script provides an easy to use interface for managing your Glype. Use the navigation above to get started.</p>
  976. <h2>Latest glype news...</h2>
  977. <iframe scrolling="no" src="{$self}?fetch=news" style="width: 100%; height:150px; border: 1px solid #ccc;" onload="setTimeout('updateLatestVersion()',1000);"></iframe>
  978. <br><br>
  979. <h2>Checking environment...</h2>
  980. <ul class="green">
  981. OUT;
  982. # Print requirements
  983. foreach ( $requirements as $li ) {
  984. echo "<li>{$li['name']}: <span class=\"bold" . ( ! $li['ok'] ? ' error-color' : '' ) . "\">{$li['value']}</span></li>\r\n";
  985. }
  986. # End requirements
  987. echo <<<OUT
  988. </ul>
  989. OUT;
  990. # How are we doing - tell user if we're OK or not.
  991. if ( $error->hasMsg() ) {
  992. 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>';
  993. } else {
  994. echo '<p><span class="bold ok-color">Environment okay</span>. You can run Glype on this server.</p>';
  995. }
  996. #
  997. # Script versions
  998. #
  999. $acpVersion = ADMIN_VERSION;
  1000. $proxyVersion = isset($CONFIG['version']) ? $CONFIG['version'] : 'unknown - pre 1.0';
  1001. # Create javascript to update the latest stable version
  1002. $javascript = <<<OUT
  1003. function updateLatestVersion(response) {
  1004. document.getElementById('current-version').innerHTML = '<img src="http://www.glype.com/feeds/proxy-version.php?cb={$cache_bust}" border="0" alt="version" />';
  1005. }
  1006. OUT;
  1007. $output->addJavascript($javascript);
  1008. # Print version summary
  1009. echo <<<OUT
  1010. <br>
  1011. <h2>Checking script versions...</h2>
  1012. <ul class="green">
  1013. <li>Control Panel version: <b>{$acpVersion}</b></li>
  1014. <li>Glype version: <b>{$proxyVersion}</b></li>
  1015. <li>Latest version: <span class="bold" id="current-version">unknown</span></li>
  1016. </ul>
  1017. OUT;
  1018. # Is the settings file up to date?
  1019. function forCompare($val) { return str_replace(' ', '', $val); }
  1020. if ( $proxyVersion != 'unknown - pre 1.0' && version_compare(forCompare($acpVersion), forCompare($proxyVersion), '>') ) {
  1021. 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>";
  1022. }
  1023. # Add footer links
  1024. $output->addFooterLinks('Glype support forum at Proxy.org', 'http://proxy.org/forum/glype-proxy/');
  1025. break;
  1026. /*****************************************************************
  1027. * SETTINGS
  1028. ******************************************************************/
  1029. case 'settings':
  1030. # Check the settings are writable
  1031. if ( ! is_writable(ADMIN_GLYPE_SETTINGS) ) {
  1032. $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>');
  1033. $tpl->disabled = ' disabled="disabled"';
  1034. }
  1035. # Load options into object
  1036. $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 unsecure 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><option key="unique_urls" type="bool" input="radio"><title>Unique URLs</title><default>false</default><desc>Generate unique URLs for each visitor. This increases privacy for the user but you cannot create links directly to proxied pages from outside the script if this option is enabled.</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.</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.</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 reponsible for requests from your proxy\'s IP address. You can use logs to record the unencoded URLs of pages visited by users in case of illegal activity undertaken through your proxy. Unencoded logging is most useful if using the unique URLs option.</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 unauthorised 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 adddress 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 limitsin both the client\'s storage space and the size of the request Cookie: header thatthe server will accept. For prolonged browsing, you may wish to store cookiesserver side to avoid this problem.</p><br><p>This has obvious privacy issues - if using this option, ensure your site clearlystates how it handles cookies and protect the cookie data from unauthorised 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 unauthorised 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>Encode URL</title><desc>Encodes the URL of the page you are viewing for increased privacy.</desc></option><option key="encodePage" default="false" force="false"><title>Encode Page</title><desc>Helps avoid filters by encoding the page before sending it and decoding it with javascript once received. This is not 100% reliable and may break functionality in some browsers.</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>\'1.3\'</default><desc>Settings file version for determining compatability with admin tool.</desc></option></section></options>');
  1037. #
  1038. # SAVE CHANGES
  1039. #
  1040. if ( $input->pSubmit && ! $error->hasMsg() ) {
  1041. # Filter inputs to create valid PHP code
  1042. function filter($value, $type) {
  1043. switch ( $type ) {
  1044. # Quote strings
  1045. case 'string':
  1046. default:
  1047. return quote($value);
  1048. # Clean integers
  1049. case 'int':
  1050. return intval($value);
  1051. # Float
  1052. case 'float':
  1053. if ( is_numeric($value) ) {
  1054. return $value;
  1055. }
  1056. return quote($value);
  1057. # Create arrays - make empty array if no value, not an array with a single empty value
  1058. case 'array':
  1059. $args = $value ? implode(', ', array_map('quote', (array) $value)) : '';
  1060. return 'array(' . $args . ')';
  1061. # Bool - check we have a real bool and resort to default if not
  1062. case 'bool':
  1063. if ( bool($value) === NULL ) {
  1064. global $option;
  1065. $value = $option->default;
  1066. }
  1067. return $value;
  1068. }
  1069. }
  1070. # Create a comment line
  1071. function comment($text, $multi=false) {
  1072. # Comment marker
  1073. $char = $multi ? '*' : '#';
  1074. # Split and make newlines with the comment char
  1075. $text = wordwrap($text, 65, "\r\n$char ");
  1076. # Return a large comment
  1077. if ( $multi ) {
  1078. return '/*****************************************************************
  1079. * ' . $text . '
  1080. ******************************************************************/';
  1081. }
  1082. # Return a small comment
  1083. return "# $text";
  1084. }
  1085. # Prepare the file hader
  1086. $toWrite = '<?php
  1087. /*******************************************************************
  1088. * Glype is copyright and trademark 2007-2012 UpsideOut, Inc. d/b/a Glype
  1089. * and/or its licensors, successors and assigners. All rights reserved.
  1090. *
  1091. * Use of Glype is subject to the terms of the Software License Agreement.
  1092. * http://www.glype.com/license.php
  1093. *******************************************************************
  1094. * Our settings file. Self-explanatory - stores the config values.
  1095. *******************************************************************
  1096. * This file has been automatically generated by the glype admin tool.
  1097. * For a more complete and thorough explanation of options, consult
  1098. * the original settings.php file from the glype package.
  1099. ******************************************************************/
  1100. ';
  1101. # Loop through all the sections
  1102. foreach ( $options->section as $section ) {
  1103. # Add section header to the file
  1104. $toWrite .= NL . NL . comment($section['name'], true) . NL;
  1105. # Now go through this section's options
  1106. foreach ( $section->option as $option ) {
  1107. $key = (string) $option['key'];
  1108. # Grab the posted value
  1109. $value = $input->{'p' . $key};
  1110. # The user-configurable options need special treatment
  1111. if ( $section['type'] == 'user' ) {
  1112. # We need to save 4 values - title, desc, default and force
  1113. $title = filter( ( isset($value['title']) ? $value['title'] : $option->title ), 'string');
  1114. $desc = filter( ( isset($value['desc']) ? $value['desc'] : $option->desc ), 'string');
  1115. $default = filter( ( isset($value['default']) ? $value['default'] : $option['default']), 'bool');
  1116. $force = isset($value['force']) ? 'true' : 'false';
  1117. # Write them
  1118. $toWrite .=
  1119. "\r\n\$CONFIG['options'][" . quote($key) . "] = array(
  1120. 'title' => $title,
  1121. 'desc' => $desc,
  1122. 'default' => $default,
  1123. 'force' => $force
  1124. );\r\n";
  1125. # Finished saving, move to next
  1126. continue;
  1127. }
  1128. # Do we have a posted value or is it forced?
  1129. if ( $value === NULL || $section['forced'] ) {
  1130. # Resort to default (which comes ready quoted)
  1131. $value = $option->default;
  1132. } else {
  1133. # Yes, apply quotes and any pre-storage logic
  1134. if ( $option->toStore && ($tmp = @eval($option->toStore)) ) {
  1135. $value = $tmp;
  1136. }
  1137. # Normalize directory paths
  1138. if ( $option->isDir ) {
  1139. # Use forward slash only
  1140. $value = str_replace('\\', '/', $value);
  1141. # Add trailing slash
  1142. if ( substr($value, -1) && substr($value, -1) != '/' ) {
  1143. $value .= '/';
  1144. }
  1145. }
  1146. # Filter it according to desired var type
  1147. $value = filter($value, $option['type']);
  1148. # Add any relativeness
  1149. if ( $option->relative && $input->{'pRelative_' . $key} ) {
  1150. $value = $option->relative['to'] . ' . ' . $value;
  1151. }
  1152. }
  1153. # Add to file (commented description and $CONFIG value)
  1154. $toWrite .= NL . comment($option->desc) . NL;
  1155. if ($key=='enable_blockscript' && $value=='true') {
  1156. $value='false';
  1157. if (function_exists('ioncube_loader_version')&&file_exists($_SERVER['DOCUMENT_ROOT'].'/blockscript/detector.php')) {$value='true';}
  1158. }
  1159. $toWrite .= '$CONFIG[' . quote($key) . '] = ' . $value . ';' . NL;
  1160. }
  1161. }
  1162. # Extract any preserved details
  1163. $file = file_get_contents(ADMIN_GLYPE_SETTINGS);
  1164. # And add to file
  1165. if ( $tmp = strpos($file, '//---PRESERVE ME---') ) {
  1166. $toWrite .= NL . substr($file, $tmp);
  1167. }
  1168. # Finished, save to file
  1169. if ( file_put_contents(ADMIN_GLYPE_SETTINGS, $toWrite) ) {
  1170. $confirm->add('The settings file has been updated.');
  1171. } else {
  1172. $error->add('The settings file failed to write. The file was detected as writable but file_put_contents() returned false.');
  1173. }
  1174. # And redirect to reload the new settings
  1175. $location->redirect('settings');
  1176. }
  1177. #
  1178. # SHOW FORM
  1179. #
  1180. # Set up page variables
  1181. $output->title = 'edit settings';
  1182. $output->bodyTitle = 'Edit settings';
  1183. # Print form
  1184. echo <<<OUT
  1185. <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>
  1186. <form action="{$self}?settings" method="post">
  1187. OUT;
  1188. # Add an "Update" button. Functionally identical to "Save".
  1189. function forCompare($val) { return str_replace(' ', '', $val); }
  1190. if ( empty($CONFIG['version']) || version_compare(forCompare(ADMIN_VERSION), forCompare($CONFIG['version']), '>') ) {
  1191. echo '<p class="error-color">Your settings file needs updating. <input class="button" type="submit" name="submit" value="Update &raquo;"></p>';
  1192. }
  1193. # Add the javascript for this page
  1194. $javascript = <<<OUT
  1195. // Create ajax "loading" image
  1196. window.loadingImage = '<img src="{$self}?image=loading.gif" width="16" height="16" alt="loading...">';
  1197. // Toggle "relative from root" option
  1198. function toggleRelative(checkbox, textId) {
  1199. var textField = document.getElementById(textId);
  1200. var relative = checkbox.value;
  1201. // Are we adding or taking away?
  1202. if ( ! checkbox.checked ) {
  1203. // Does the field already contain the relative path?
  1204. if ( textField.value.indexOf(relative) != 0 ) {
  1205. textField.value = relative += textField.value;
  1206. }
  1207. } else {
  1208. textField.value = textField.value.replace(relative, '');
  1209. }
  1210. }
  1211. // Check if a given directory exists / is_writable
  1212. var testDir = function(fieldName) {
  1213. // Save vars in object
  1214. this.input = document.getElementById(fieldName);
  1215. this.result = document.getElementById('dircheck_' + fieldName);
  1216. this.relative = document.getElementById('relative_' + fieldName);
  1217. this.isTmp = fieldName == 'tmp_dir';
  1218. // Update status
  1219. this.updateDirStatus = function(response) {
  1220. this.result.innerHTML = response;
  1221. }
  1222. // Run when value is changed
  1223. this.changed = function() {
  1224. this.isRelative = this.relative ? this.relative.checked : false;
  1225. // Attempt to get path from the value
  1226. var dirPath = this.input.value;
  1227. // Is it relative?
  1228. if ( this.isRelative ) {
  1229. dirPath = this.relative.value + dirPath;
  1230. }
  1231. // Update with the loading .gif
  1232. this.result.innerHTML = loadingImage;
  1233. // Make the request
  1234. runAjax('$self?fetch=test-dir&dir=' + encodeURIComponent(dirPath) + '&tmp=' + this.isTmp, null, this.updateDirStatus, this);
  1235. }
  1236. }
  1237. OUT;
  1238. $output->addJavascript($javascript);
  1239. # Go through all options and print the form
  1240. foreach ( $options->section as $section ) {
  1241. # Print title if we're displaying this
  1242. if ( $section['hidden'] === NULL ) {
  1243. echo '
  1244. <br>
  1245. <div class="hr"></div>
  1246. <h2>' . $section['name'] . '</h2>';
  1247. }
  1248. # What type of section is this?
  1249. switch ( $section['type'] ) {
  1250. # Standard option/value pairs
  1251. case 'settings':
  1252. # Comment
  1253. if ( $section->comment ) {
  1254. echo '<div class="comment">',$section->comment,'</div>';
  1255. }
  1256. # Print table header
  1257. echo <<<OUT
  1258. <table class="form_table" border="0" cellpadding="0" cellspacing="0">
  1259. OUT;
  1260. # Loop through the child options
  1261. foreach ( $section->option as $option ) {
  1262. # Reset variables
  1263. $field = '';
  1264. # Convert to string so we can use it as an array index
  1265. $key = (string) $option['key'];
  1266. # Find current value (if we have one)
  1267. $currentValue = isset($CONFIG[$key]) ? $CONFIG[$key] : @eval('return ' . $option->default . ';');
  1268. # If the option can be relative, find out what we're relative from
  1269. if ( $option->relative ) {
  1270. # Run code from options XML to get value
  1271. $relativeTo = @eval('return ' . $option->relative['to'] . ';');
  1272. # Remove that from the current value
  1273. $currentValue = str_replace($relativeTo, '', $currentValue, $relativeChecked);
  1274. }
  1275. # If the option has any "toDisplay" filtering, apply it
  1276. if ( $option->toDisplay && ( $newValue = @eval($option->toDisplay) ) !== false ) {
  1277. $currentValue = $newValue;
  1278. }
  1279. # Create attributes (these are fairly consistent in multiple option types)
  1280. $attr = <<<OUT
  1281. type="{$option['input']}" name="{$option['key']}" id="{$option['key']}" value="{$currentValue}" class="inputgri {$option['styles']}"
  1282. OUT;
  1283. # Prepare the input
  1284. switch ( $option['input'] ) {
  1285. # TEXT FIELD
  1286. case 'text':
  1287. # Add onchange to test dirs
  1288. if ( $option->isDir ) {
  1289. $attr .= " onchange=\"test{$option['key']}.changed()\"";
  1290. }
  1291. $field = '<input' . $attr . '>';
  1292. # Can we be relative to another variable?
  1293. if ( $option->relative ) {
  1294. # Is the box already checked?
  1295. $checked = empty($relativeChecked) ? '' : ' checked="checked"';
  1296. # Escape backslashes so we can use it in javascript
  1297. $relativeToEscaped = str_replace('\\', '\\\\', $relativeTo);
  1298. # Add to existing field
  1299. $field .= <<<OUT
  1300. <input type="checkbox" onclick="toggleRelative(this,'{$option['key']}')" value="{$relativeTo}" name="relative_{$option['key']}" id="relative_{$option['key']}"{$checked}>
  1301. <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>
  1302. OUT;
  1303. }
  1304. break;
  1305. # SELECT FIELD
  1306. case 'select':
  1307. $field = '<select' . $attr . '>' . @eval($option->generateOptions). '</select>';
  1308. break;
  1309. # RADIO
  1310. case 'radio':
  1311. $onChecked = $currentValue ? ' checked="checked"' : '';
  1312. $offChecked = ! $currentValue ? ' checked="checked"' : '';
  1313. $field = <<<OUT
  1314. <input type="radio" name="{$option['key']}" id="{$option['key']}_on" value="true" class="inputgri {$option['styles']}"{$onChecked}>
  1315. <label for="{$option['key']}_on">Yes</label>
  1316. &nbsp; / &nbsp;
  1317. <input type="radio" name="{$option['key']}" id="{$option['key']}_off" value="false" class="inputgri {$option['styles']}"{$offChecked}>
  1318. <label for="{$option['key']}_off">No</label>
  1319. OUT;
  1320. break;
  1321. # TEXTAREA
  1322. case 'textarea':
  1323. $field = '<textarea ' . $attr . '>' . $currentValue . '</textarea><br>';
  1324. break;
  1325. }
  1326. # Is there a description to use as tooltip?
  1327. $tooltip = $option->desc ? 'class="tooltip" onmouseover="tooltip(\'' . htmlentities(addslashes($option->desc), ENT_QUOTES) . '\')" onmouseout="exit()"' : '';
  1328. # Add units
  1329. if ( $option['unit'] ) {
  1330. $field .= ' ' . $option['unit'];
  1331. }
  1332. # Any after field text to add?
  1333. if ( $option->afterField ) {
  1334. # Code to eval or string?
  1335. $add = $option->afterField['eval'] ? @eval($option->afterField) : $option->afterField;
  1336. # Add to field
  1337. if ( $add ) {
  1338. $field .= ' (<span class="little">' . $add . '</span>)';
  1339. }
  1340. }
  1341. echo <<<OUT
  1342. <tr>
  1343. <td width="160" align="right">
  1344. <label for="{$option['key']}" {$tooltip}>{$option->title}:</label>
  1345. </td>
  1346. <td>{$field}</td>
  1347. </tr>
  1348. OUT;
  1349. # Is this a directory path we're expecting?
  1350. if ( $option->isDir ) {
  1351. # Write with javascript to hide from non-js browsers
  1352. $write = jsWrite('(<a style="cursor:pointer;" onclick="test' . $option['key'] . '.changed()">try again</a>)');
  1353. echo <<<OUT
  1354. <tr>
  1355. <td>&nbsp;</td>
  1356. <td>
  1357. &nbsp;&nbsp;
  1358. <span id="dircheck_{$option['key']}"></span>
  1359. $write
  1360. </td>
  1361. </tr>
  1362. OUT;
  1363. $output->addDomReady("window.test{$option['key']} = new testDir('{$option['key']}');test{$option['key']}.changed();");
  1364. }
  1365. }
  1366. echo '</table>';
  1367. break;
  1368. # User configurable options
  1369. case 'user':
  1370. # Print table header
  1371. echo <<<OUT
  1372. <table class="table" cellpadding="0" cellspacing="0">
  1373. <tr class="table_header">
  1374. <td width="200">Title</td>
  1375. <td width="50">Default</td>
  1376. <td>Description</td>
  1377. <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>
  1378. </tr>
  1379. OUT;
  1380. # Find the current options
  1381. $currentOptions = isset($CONFIG['options']) ? $CONFIG['options'] : array();
  1382. # Print options
  1383. foreach ( $section->option as $option ) {
  1384. # Get values from XML
  1385. $key = (string) $option['key'];
  1386. # Get values from current settings, resorted to XML if not available
  1387. $title = isset($currentOptions[$key]['title']) ? $currentOptions[$key]['title'] : $option->title;
  1388. $default = isset($currentOptions[$key]['default']) ? $currentOptions[$key]['default'] : bool($option['default']);
  1389. $desc = isset($currentOptions[$key]['desc']) ? $currentOptions[$key]['desc'] : $option->desc;
  1390. $force = isset($currentOptions[$key]['force']) ? $currentOptions[$key]['force'] : bool($option['force']);
  1391. # Determine checkboxes
  1392. $on = $default == true ? ' checked="checked"' : '';
  1393. $off = $default == false ? ' checked="checked"' : '';
  1394. $force = $force ? ' checked="checked"' : '';
  1395. # Row colour
  1396. $row = isset($row) && $row == 'row1' ? 'row2' : 'row1';
  1397. echo <<<OUT
  1398. <tr class="{$row}">
  1399. <td><input type="text" class="inputgri" style="width:95%;" name="{$key}[title]" value="{$title}"></td>
  1400. <td>
  1401. <input type="radio" name="{$key}[default]" value="true" id="options_{$key}_on"{$on}> <label for="options_{$key}_on">On</label>
  1402. <br>
  1403. <input type="radio" name="{$key}[default]" value="false" id="options_{$key}_off"{$off}> <label for="options_{$key}_off">Off</label>
  1404. </td>
  1405. <td><textarea class="inputgri wide-input" name="{$key}[desc]">{$desc}</textarea></td>
  1406. <td><input type="checkbox" name="{$key}[force]"{$force} value="true"></td>
  1407. </tr>
  1408. OUT;
  1409. }
  1410. # Print table footer
  1411. echo '</table>';
  1412. break;
  1413. }
  1414. }
  1415. # Page footer
  1416. echo <<<OUT
  1417. <div class="hr"></div>
  1418. <p class="center"><input class="button" type="submit" name="submit" value="Save Changes"{$tpl->disabled}></p>
  1419. </form>
  1420. <div class="hr"></div>
  1421. <h2><a name="notes"></a>Notes:</h2>
  1422. <ul class="black">
  1423. <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>
  1424. <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 unauthorised access by choosing a suitable location above the webroot or using .htaccess files to deny access.</li>
  1425. <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>
  1426. <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>
  1427. </ul>
  1428. <p class="right"><a href="#">^ top</a></p>
  1429. OUT;
  1430. break;
  1431. /*****************************************************************
  1432. * BlockScript
  1433. ******************************************************************/
  1434. case 'blockscript':
  1435. if (file_exists($bsc=$_SERVER['DOCUMENT_ROOT'].'/blockscript/tmp/config.php')) {
  1436. include($bsc);
  1437. # header('Location: /blockscript/detector.php?blockscript=setup&bsap='.$BS_VAL['admin_password']); exit;
  1438. }
  1439. $installed = isset($BS_VAL['license_agreement_accepted']) ? '<span class="ok-color">installed</span>' : '<span class="error-color">not installed</span>';
  1440. $enabled = (isset($BS_VAL['license_agreement_accepted']) && !empty($CONFIG['enable_blockscript'])) ? '<span class="ok-color">enabled</span>' : '<span class="error-color">disabled</span>';
  1441. if (!($ok=function_exists('ioncube_loader_version'))) {$error->add('BlockScript requires IonCube.');}
  1442. $IonCubeVersion = $ok && ( $tmp = ioncube_loader_version() ) ? $tmp : 'not available';
  1443. if ($ok && $tmp!='not available') {
  1444. }
  1445. # Print header
  1446. $output->title = 'BlockScript&reg;';
  1447. $output->bodyTitle = 'BlockScript&reg; Integration';
  1448. echo <<<OUT
  1449. <form action="{$self}?blockscript" method="post">
  1450. <table class="form_table" border="0" cellpadding="0" cellspacing="0">
  1451. <tr>
  1452. <td align="right">BlockScript status:</td>
  1453. <td><b>{$installed} and {$enabled}</b></td>
  1454. </tr>
  1455. </table>
  1456. </form>
  1457. <div class="hr"></div>
  1458. <h2>About</h2>
  1459. <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>
  1460. <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>
  1461. <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>
  1462. <div class="hr"></div>
  1463. <h2>Installation Instructions</h2>
  1464. <ol>
  1465. <li><a href="https://www.blockscript.com/download.php" target="_blank">Download BlockScript</a> and extract the contents of the .zip file.</li>
  1466. <li>Upload the &quot;blockscript&quot; directory and its contents.</li>
  1467. <li>CHMOD 0777 (or 0755 if running under suPHP) the &quot;detector.php&quot; file and the &quot;/blockscript/tmp/&quot; directory.</li>
  1468. <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>
  1469. <li>Follow the on-screen prompts in your BlockScript control panel.</li>
  1470. </ol>
  1471. <br>
  1472. OUT;
  1473. if ($bsc) {
  1474. $admin_password = isset($BS_VAL['admin_password']) ? $BS_VAL['admin_password'] : '';
  1475. 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>';
  1476. }
  1477. break;
  1478. /*****************************************************************
  1479. * LOG INDEX
  1480. ******************************************************************/
  1481. case 'logs':
  1482. # Are we updating the log destination?
  1483. if ( $input->pDestination !== NULL ) {
  1484. # Attempt to validate path
  1485. $path = realpath($input->pDestination);
  1486. # Is the path OK?
  1487. if ( $path ) {
  1488. $confirm->add('Log folder updated.');
  1489. } else {
  1490. $error->add('Log folder not updated. <b>' . $input->pDestination . '</b> does not exist.');
  1491. }
  1492. # Normalize
  1493. $path = str_replace('\\', '/', $path);
  1494. # Add trailing slash
  1495. if ( isset($path[strlen($path)-1]) && $path[strlen($path)-1] != '/' ) {
  1496. $path .= '/';
  1497. }
  1498. # Save in session
  1499. $_SESSION['logging_destination'] = $path;
  1500. # Redirect to avoid "Resend Post?" on refresh
  1501. $location->redirect('logs');
  1502. }
  1503. # Find status
  1504. $enabled = empty($CONFIG['enable_logging']) == false;
  1505. $status = $enabled ? '<span class="ok-color">enabled</span>' : '<span class="error-color">disabled</span>';
  1506. $destination = isset($CONFIG['logging_destination']) ? $CONFIG['logging_destination'] : '';
  1507. # Are we overriding the real destination with some other value?
  1508. if ( ! empty($_SESSION['logging_destination']) ) {
  1509. $destination = $_SESSION['logging_destination'];
  1510. }
  1511. # Print header
  1512. $output->title = 'log viewer';
  1513. $output->bodyTitle = 'Logging';
  1514. echo <<<OUT
  1515. <form action="{$self}?logs" method="post">
  1516. <table class="form_table" border="0" cellpadding="0" cellspacing="0">
  1517. <tr>
  1518. <td align="right">Logging feature:</td>
  1519. <td><b>{$status}</b></td>
  1520. </tr>
  1521. <tr>
  1522. <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>
  1523. <td><input type="text" name="destination" class="inputgri wide-input" value="{$destination}"> <input type="submit" class="button" value="Update &raquo;"></td>
  1524. </tr>
  1525. </table>
  1526. </form>
  1527. <div class="hr"></div>
  1528. <h2>Log files</h2>
  1529. OUT;
  1530. # Do we have any log files to analyse?
  1531. if ( ! ( file_exists($destination) && is_dir($destination) && ($logFiles = scandir($destination, 1)) ) ) {
  1532. # Print none and exit
  1533. echo '<p>No log files to analyse.</p>';
  1534. break;
  1535. }
  1536. # Print starting table
  1537. echo <<<OUT
  1538. <table class="table" cellpadding="0" cellspacing="0">
  1539. OUT;
  1540. # Set up starting vars
  1541. $currentYearMonth = false;
  1542. $first = true;
  1543. $totalSize = 0;
  1544. # Go through all files
  1545. foreach ( $logFiles as $file ) {
  1546. # Verify files is a glype log. Log files formatted as YYYY-MM-DD.log
  1547. if ( ! ( strlen($file) == 14 && preg_match('#^([0-9]{4})-([0-9]{2})-([0-9]{2})\.log$#', $file, $matches) ) ) {
  1548. continue;
  1549. }
  1550. # Extract matches
  1551. list(, $yearNumeric, $monthNumeric, $dayNumeric) = $matches;
  1552. # Convert filename to timestamp
  1553. $timestamp = strtotime(str_replace('.log', ' 12:00 PM', $file));
  1554. # Extract time parts
  1555. $month = date('F', $timestamp);
  1556. $day = date('l', $timestamp);
  1557. $display = date('jS', $timestamp) . ' (' . $day . ')';
  1558. $yearMonth = $yearNumeric . '-' . $monthNumeric;
  1559. # Display in bold if today
  1560. if ( $display == date('jS (l)') ) {
  1561. $display = '<b>' . $display . '</b>';
  1562. }
  1563. # Is this a new month?
  1564. if ( $yearMonth != $currentYearMonth ) {
  1565. # Print in a separate table (unless first)
  1566. if ( $first == false ) {
  1567. echo <<<OUT
  1568. </table>
  1569. <br>
  1570. <table class="table" cellpadding="0" cellspacing="0">
  1571. OUT;
  1572. }
  1573. # Print table header
  1574. echo <<<OUT
  1575. <tr class="table_header">
  1576. <td colspan="2">{$month} {$yearNumeric}</td>
  1577. <td>[<a href="{$self}?logs-view&month={$yearMonth}&show=popular-sites">popular sites</a>]</td>
  1578. </tr>
  1579. OUT;
  1580. # Update vars so we don't do this again until we want to
  1581. $currentYearMonth = $yearMonth;
  1582. $first = false;
  1583. }
  1584. # Format size
  1585. $filesize = filesize($destination . $file);
  1586. $totalSize += $filesize;
  1587. $size = formatSize($filesize);
  1588. # Row colour is grey if weekend
  1589. $row = ( $day == 'Saturday' || $day == 'Sunday' ) ? '3' : '1';
  1590. # Print log file row
  1591. echo <<<OUT
  1592. <tr class="row{$row}">
  1593. <td width="150">{$display}</td>
  1594. <td width="100">{$size}</td>
  1595. <td>
  1596. [<a href="{$self}?logs-view&file={$file}&show=raw" target="_blank" title="Opens in a new window">raw log</a>]
  1597. &nbsp;
  1598. [<a href="{$self}?logs-view&file={$file}&show=popular-sites">popular sites</a>]
  1599. </td>
  1600. </tr>
  1601. OUT;
  1602. }
  1603. # End table
  1604. $total = formatSize($totalSize);
  1605. echo <<<OUT
  1606. </table>
  1607. <p>Total space used by logs: <b>{$total}</b></p>
  1608. <p class="little">Note: Raw logs open in a new window.</p>
  1609. <p class="little">Note: You can set up your proxy to automatically delete old logs with the maintenance feature.</p>
  1610. OUT;
  1611. break;
  1612. /*****************************************************************
  1613. * LOG VIEWER
  1614. ******************************************************************/
  1615. case 'logs-view':
  1616. $output->title = 'view log';
  1617. $output->bodyTitle = 'View log file';
  1618. # Find log folder
  1619. $logFolder = isset($_SESSION['logging_destination']) ? $_SESSION['logging_destination'] : $CONFIG['logging_destination'];
  1620. # Verify folder is valid
  1621. if ( ! file_exists($logFolder) || ! is_dir($logFolder) ) {
  1622. $error->add('The log folder specified does not exist.');
  1623. break;
  1624. }
  1625. # Find file
  1626. $file = $input->gFile ? realpath($logFolder . '/' . str_replace('..', '', $input->gFile)) : false;
  1627. # What type of viewing do we want?
  1628. switch ( $input->gShow ) {
  1629. # Raw log file
  1630. case 'raw':
  1631. # Find file
  1632. if ( $file == false || file_exists($file) == false ) {
  1633. $error->add('The file specified does not exist.');
  1634. break;
  1635. }
  1636. # Use raw wrapper
  1637. $output = new RawOutput;
  1638. # And load file
  1639. readfile($file);
  1640. break;
  1641. # Stats - most visited site
  1642. case 'popular-sites':
  1643. # Scan files to find most popular sites
  1644. $scan = array();
  1645. # Find files to scan
  1646. if ( $file ) {
  1647. # Single file mode
  1648. $scan[] = $file;
  1649. # Date of log file
  1650. $date = ( $fileTime = strtotime(basename($input->gFile, '.log')) ) ? date('l jS F, Y', $fileTime) : '[unknown]';
  1651. } else if ( $input->gMonth && strlen($input->gMonth) > 5 && ( $logFiles = scandir($logFolder) ) ) {
  1652. # Month mode - use all files in given month
  1653. foreach ( $logFiles as $file ) {
  1654. # Match name
  1655. if ( strpos($file, $input->gMonth) === 0 ) {
  1656. $scan[] = realpath($logFolder . '/' . $file);
  1657. }
  1658. }
  1659. # Date of log files
  1660. $date = date('F Y', strtotime($input->gMonth . '-01'));
  1661. }
  1662. # Check we have some files to scan
  1663. if ( empty($scan) ) {
  1664. $error->add('No files to analyse.');
  1665. break;
  1666. }
  1667. # Data array
  1668. $visited = array();
  1669. # Read through files
  1670. foreach ( $scan as $file ) {
  1671. # Allow extra time
  1672. @set_time_limit(30);
  1673. # Open handle to file
  1674. if ( ( $handle = fopen($file, 'rb') ) === false ) {
  1675. continue;
  1676. }
  1677. # Scan for URLs
  1678. while ( ( $data = fgetcsv($handle, 2000) ) !== false ) {
  1679. # Extract URLs
  1680. if ( isset($data[2]) && preg_match('#(?:^|\.)([a-z0-9-]+\.(?:[a-z]{2,}|[a-z.]{5,6}))$#i', strtolower(parse_url($data[2], PHP_URL_HOST)), $tmp) ) {
  1681. # Add to tally
  1682. if ( isset($visited[$tmp[1]]) ) {
  1683. # Increment an existing count
  1684. ++$visited[$tmp[1]];
  1685. } else {
  1686. # Create a new item
  1687. $visited[$tmp[1]] = 1;
  1688. }
  1689. }
  1690. }
  1691. # Close handle to free resources
  1692. fclose($handle);
  1693. }
  1694. # Sort
  1695. arsort($visited);
  1696. # Truncate to first X results
  1697. $others = array_splice($visited, ADMIN_STATS_LIMIT);
  1698. # Sum up the "others" group
  1699. $others = array_sum($others);
  1700. # Print header
  1701. echo <<<OUT
  1702. <h2>Most visited sites for {$date}</h2>
  1703. <table class="form_table" cellpadding="0" cellspacing="0" width="100%">
  1704. OUT;
  1705. # Find largest value
  1706. $max = max($visited);
  1707. # Create horizontal bar chart type thing
  1708. foreach ( $visited as $site => $count ) {
  1709. $rowWidth = round(($count/$max)*100);
  1710. # Print it
  1711. echo <<<OUT
  1712. <tr>
  1713. <td width="200" align="right">{$site}</td>
  1714. <td><div class="bar" style="width: {$rowWidth}%;">{$count}</div></td>
  1715. </tr>
  1716. OUT;
  1717. }
  1718. # Table footer
  1719. echo <<<OUT
  1720. <tr>
  1721. <td align="right"><i>Others</i></td>
  1722. <td>{$others}</td>
  1723. </tr>
  1724. </table>
  1725. <p class="align-center">&laquo; <a href="{$self}?logs">Back</a></p>
  1726. OUT;
  1727. break;
  1728. # Anything else - ignore
  1729. default:
  1730. $error->add('Missing input. No log view specified.');
  1731. }
  1732. break;
  1733. /*****************************************************************
  1734. * Everything else - 404
  1735. ******************************************************************/
  1736. default:
  1737. # Send 404 status
  1738. $output->sendStatus(404);
  1739. # And print the error page
  1740. $output->title = 'page not found';
  1741. $output->bodyTitle = 'Page Not Found (404)';
  1742. echo <<<OUT
  1743. <p>The requested page <b>{$_SERVER['REQUEST_URI']}</b> was not found.</p>
  1744. OUT;
  1745. }
  1746. /*****************************************************************
  1747. * Send content wrapped in our theme
  1748. ******************************************************************/
  1749. # Get buffer
  1750. $content = ob_get_contents();
  1751. # Clear buffer
  1752. ob_end_clean();
  1753. # Add content
  1754. $output->addContent($content);
  1755. # And print
  1756. $output->out();