PageRenderTime 46ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/ezUser.php

https://bitbucket.org/dominicsayers/ezuser
PHP | 4848 lines | 3186 code | 596 blank | 1066 comment | 529 complexity | c1fa27ac6a012155e52c1ac5d417d03a MD5 | raw file
  1. <?php
  2. /**
  3. * Enables user registration and authentication for a website
  4. *
  5. * This code has three principle design goals:
  6. *
  7. * 1. To make it easy for people to register and sign in to your site.
  8. * 2. To make it easy for you to add this functionality to your site.
  9. * 3. To make it easy for you to administer the user database on your site.
  10. *
  11. * Other design goals, such as run-time efficiency, are important but secondary to
  12. * these.
  13. *
  14. * Copyright (c) 2008-2010, Dominic Sayers <br>
  15. * All rights reserved.
  16. *
  17. * Redistribution and use in source and binary forms, with or without modification,
  18. * are permitted provided that the following conditions are met:
  19. *
  20. * - Redistributions of source code must retain the above copyright notice,
  21. * this list of conditions and the following disclaimer.
  22. * - Redistributions in binary form must reproduce the above copyright notice,
  23. * this list of conditions and the following disclaimer in the documentation
  24. * and/or other materials provided with the distribution.
  25. * - Neither the name of Dominic Sayers nor the names of its contributors may be
  26. * used to endorse or promote products derived from this software without
  27. * specific prior written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  30. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  31. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  32. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  33. * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  34. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  35. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  36. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  37. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  38. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  39. *
  40. * @package ezUser
  41. * @author Dominic Sayers <dominic@sayers.cc>
  42. * @copyright 2008-2010 Dominic Sayers
  43. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  44. * @link http://code.google.com/p/ezuser/
  45. * @version 0.25.5 - Floating div dialog boxes
  46. */
  47. // The quality of this code has been improved greatly by using PHPLint
  48. // PHPLint is copyright (c) 2009 Umberto Salsi
  49. // PHPLint is free software; see the license for copying conditions.
  50. // More info: http://www.icosaedro.it/phplint/
  51. /*.
  52. require_module 'dom';
  53. require_module 'pcre';
  54. require_module 'hash';
  55. require_module 'session';
  56. .*/
  57. /* Comment out profiling statements if not needed
  58. function ezuser_time() {list($usec, $sec) = explode(" ",microtime()); return ((float)$usec + (float)$sec);}
  59. $ezuser_profile = array();
  60. $ezuser_profile['REQUEST_TIME'] = $_SERVER['REQUEST_TIME'];
  61. $ezuser_profile['received'] = ezuser_time();
  62. */
  63. /**
  64. * Common utility functions
  65. *
  66. * @package ezUser
  67. * @version 1.14 (revision number of this common functions class only)
  68. */
  69. interface I_ezUser_common {
  70. // const PACKAGE = 'ezUser',
  71. // VERSION = '0.25', // Version 1.13: added
  72. // Version 1.14: PACKAGE & VERSION now hard-coded by build process.
  73. const HASH_FUNCTION = 'SHA256',
  74. URL_SEPARATOR = '/',
  75. // Behaviour settings for strleft()
  76. STRLEFT_MODE_NONE = 0,
  77. STRLEFT_MODE_ALL = 1,
  78. // Behaviour settings for getURL()
  79. URL_MODE_PROTOCOL = 1,
  80. URL_MODE_HOST = 2,
  81. URL_MODE_PORT = 4,
  82. URL_MODE_PATH = 8,
  83. URL_MODE_ALL = 15,
  84. // Behaviour settings for getPackage()
  85. // PACKAGE_CASE_DEFAULT = 0,
  86. //// PACKAGE_CASE_LOWER = 0,
  87. // PACKAGE_CASE_CAMEL = 1,
  88. // PACKAGE_CASE_UPPER = 2,
  89. // Version 1.14: PACKAGE & VERSION now hard-coded by build process.
  90. // Extra GLOB constant for safe_glob()
  91. GLOB_NODIR = 256,
  92. GLOB_PATH = 512,
  93. GLOB_NODOTS = 1024,
  94. GLOB_RECURSE = 2048,
  95. // Email validation constants
  96. ISEMAIL_VALID = 0,
  97. ISEMAIL_TOOLONG = 1,
  98. ISEMAIL_NOAT = 2,
  99. ISEMAIL_NOLOCALPART = 3,
  100. ISEMAIL_NODOMAIN = 4,
  101. ISEMAIL_ZEROLENGTHELEMENT = 5,
  102. ISEMAIL_BADCOMMENT_START = 6,
  103. ISEMAIL_BADCOMMENT_END = 7,
  104. ISEMAIL_UNESCAPEDDELIM = 8,
  105. ISEMAIL_EMPTYELEMENT = 9,
  106. ISEMAIL_UNESCAPEDSPECIAL = 10,
  107. ISEMAIL_LOCALTOOLONG = 11,
  108. ISEMAIL_IPV4BADPREFIX = 12,
  109. ISEMAIL_IPV6BADPREFIXMIXED = 13,
  110. ISEMAIL_IPV6BADPREFIX = 14,
  111. ISEMAIL_IPV6GROUPCOUNT = 15,
  112. ISEMAIL_IPV6DOUBLEDOUBLECOLON = 16,
  113. ISEMAIL_IPV6BADCHAR = 17,
  114. ISEMAIL_IPV6TOOMANYGROUPS = 18,
  115. ISEMAIL_TLD = 19,
  116. ISEMAIL_DOMAINEMPTYELEMENT = 20,
  117. ISEMAIL_DOMAINELEMENTTOOLONG = 21,
  118. ISEMAIL_DOMAINBADCHAR = 22,
  119. ISEMAIL_DOMAINTOOLONG = 23,
  120. ISEMAIL_TLDNUMERIC = 24,
  121. ISEMAIL_DOMAINNOTFOUND = 25;
  122. // ISEMAIL_NOTDEFINED = 99;
  123. // Basic utility functions
  124. public static /*.string.*/ function strleft(/*.string.*/ $haystack, /*.string.*/ $needle);
  125. public static /*.mixed.*/ function getInnerHTML(/*.string.*/ $html, /*.string.*/ $tag);
  126. public static /*.array[string][string]string.*/ function meta_to_array(/*.string.*/ $html);
  127. public static /*.string.*/ function var_dump_to_HTML(/*.string.*/ $var_dump, $offset = 0);
  128. public static /*.string.*/ function array_to_HTML(/*.array[]mixed.*/ $source = NULL);
  129. // Session functions
  130. public static /*.void.*/ function checkSession();
  131. // Environment functions
  132. // public static /*.string.*/ function getPackage($mode = self::PACKAGE_CASE_DEFAULT); // Version 1.14: PACKAGE & VERSION now hard-coded by build process.
  133. public static /*.string.*/ function getURL($mode = self::URL_MODE_PATH, $filename = '');
  134. public static /*.string.*/ function docBlock_to_HTML(/*.string.*/ $php);
  135. // File system functions
  136. public static /*.mixed.*/ function safe_glob(/*.string.*/ $pattern, /*.int.*/ $flags = 0);
  137. public static /*.string.*/ function getFileContents(/*.string.*/ $filename, /*.int.*/ $flags = 0, /*.object.*/ $context = NULL, /*.int.*/ $offset = -1, /*.int.*/ $maxLen = -1);
  138. public static /*.string.*/ function findIndexFile(/*.string.*/ $folder);
  139. public static /*.string.*/ function findTarget(/*.string.*/ $target);
  140. // Data functions
  141. public static /*.string.*/ function makeId();
  142. public static /*.string.*/ function makeUniqueKey(/*.string.*/ $id);
  143. public static /*.string.*/ function mt_shuffle(/*.string.*/ $str, /*.int.*/ $seed = 0);
  144. // public static /*.void.*/ function mt_shuffle_array(/*.array.*/ &$arr, /*.int.*/ $seed = 0);
  145. public static /*.string.*/ function prkg(/*.int.*/ $index, /*.int.*/ $length = 6, /*.int.*/ $base = 34, /*.int.*/ $seed = 0);
  146. // Validation functions
  147. // public static /*.boolean.*/ function is_email(/*.string.*/ $email, $checkDNS = false);
  148. public static /*.mixed.*/ function is_email(/*.string.*/ $email, $checkDNS = false, $diagnose = false); // New parameters from version 1.8
  149. }
  150. /**
  151. * Common utility functions
  152. */
  153. abstract class ezUser_common implements I_ezUser_common {
  154. /**
  155. * Return the beginning of a string, up to but not including the search term.
  156. *
  157. * @param string $haystack The string containing the search term
  158. * @param string $needle The end point of the returned string. In other words, if <var>needle</var> is found then the begging of <var>haystack</var> is returned up to the character before <needle>.
  159. * @param int $mode If <var>needle</var> is not found then <pre>FALSE</pre> will be returned. */
  160. public static /*.string.*/ function strleft(/*.string.*/ $haystack, /*.string.*/ $needle, /*.int.*/ $mode = self::STRLEFT_MODE_NONE) {
  161. $posNeedle = strpos($haystack, $needle);
  162. if ($posNeedle === false) {
  163. if ($mode === self::STRLEFT_MODE_ALL)
  164. return $haystack;
  165. else
  166. return (string) $posNeedle;
  167. } else
  168. return substr($haystack, 0, $posNeedle);
  169. }
  170. /**
  171. * Return the contents of an HTML element, the first one matching the <var>tag</var> parameter.
  172. *
  173. * @param string $html The string containing the html to be searched
  174. * @param string $tag The type of element to search for. The contents of first matching element will be returned. If the element doesn't exist then <var>false</var> is returned.
  175. */
  176. public static /*.mixed.*/ function getInnerHTML(/*.string.*/ $html, /*.string.*/ $tag) {
  177. $pos_tag_open_start = stripos($html, "<$tag") ; if ($pos_tag_open_start === false) return false;
  178. $pos_tag_open_end = strpos($html, '>', $pos_tag_open_start) ; if ($pos_tag_open_end === false) return false;
  179. $pos_tag_close = stripos($html, "</$tag>", $pos_tag_open_end) ; if ($pos_tag_close === false) return false;
  180. return substr($html, $pos_tag_open_end + 1, $pos_tag_close - $pos_tag_open_end - 1);
  181. }
  182. /**
  183. * Return the <var>meta</var> tags from an HTML document as an array.
  184. *
  185. * The array returned will have a 'key' element which is an array of name/value pairs representing all the metadata
  186. * from the HTML document. If there are any <var>name</var> or <var>http-equiv</var> meta elements
  187. * these will be in their own sub-array. The 'key' sub-array combines all meta tags.
  188. *
  189. * Qualifying attributes such as <var>lang</var> and <var>scheme</var> have their own sub-arrays with the same key
  190. * as the main sub-array.
  191. *
  192. * Here are some example meta tags:
  193. *
  194. * <pre>
  195. * <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
  196. * <meta name="description" content="Free Web tutorials" />
  197. * <meta name="keywords" content="HTML,CSS,XML,JavaScript" />
  198. * <meta name="author" content="Hege Refsnes" />
  199. * <meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1" />
  200. * <META NAME="ROBOTS" CONTENT="NOYDIR">
  201. * <META NAME="Slurp" CONTENT="NOYDIR">
  202. * <META name="author" content="John Doe">
  203. * <META name ="copyright" content="&copy; 1997 Acme Corp.">
  204. * <META name= "keywords" content="corporate,guidelines,cataloging">
  205. * <META name = "date" content="1994-11-06T08:49:37+00:00">
  206. * <meta name="DC.title" lang="en" content="Services to Government" >
  207. * <meta name="DCTERMS.modified" scheme="XSD.date" content="2007-07-22" >
  208. * <META http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
  209. * <META name="geo.position" content="26.367559;-80.12172">
  210. * <META name="geo.region" content="US-FL">
  211. * <META name="geo.placename" content="Boca Raton, FL">
  212. * <META name="ICBM" content="26.367559, -80.12172">
  213. * <META name="DC.title" content="THE NAME OF YOUR SITE">
  214. * </pre>
  215. *
  216. * Here is a dump of the returned array:
  217. *
  218. * <pre>
  219. * array (
  220. * 'key' =>
  221. * array (
  222. * 'Content-Type' => 'text/html; charset=iso-8859-1',
  223. * 'description' => 'Free Web tutorials',
  224. * 'keywords' => 'corporate,guidelines,cataloging',
  225. * 'author' => 'John Doe',
  226. * 'ROBOTS' => 'NOYDIR',
  227. * 'Slurp' => 'NOYDIR',
  228. * 'copyright' => '&copy; 1997 Acme Corp.',
  229. * 'date' => '1994-11-06T08:49:37+00:00',
  230. * 'DC.title' => 'THE NAME OF YOUR SITE',
  231. * 'DCTERMS.modified' => '2007-07-22',
  232. * 'geo.position' => '26.367559;-80.12172',
  233. * 'geo.region' => 'US-FL',
  234. * 'geo.placename' => 'Boca Raton, FL',
  235. * 'ICBM' => '26.367559, -80.12172',
  236. * ),
  237. * 'http-equiv' =>
  238. * array (
  239. * 'Content-Type' => 'text/html; charset=iso-8859-1',
  240. * ),
  241. * 'name' =>
  242. * array (
  243. * 'description' => 'Free Web tutorials',
  244. * 'keywords' => 'corporate,guidelines,cataloging',
  245. * 'author' => 'John Doe',
  246. * 'ROBOTS' => 'NOYDIR',
  247. * 'Slurp' => 'NOYDIR',
  248. * 'copyright' => '&copy; 1997 Acme Corp.',
  249. * 'date' => '1994-11-06T08:49:37+00:00',
  250. * 'DC.title' => 'THE NAME OF YOUR SITE',
  251. * 'DCTERMS.modified' => '2007-07-22',
  252. * 'geo.position' => '26.367559;-80.12172',
  253. * 'geo.region' => 'US-FL',
  254. * 'geo.placename' => 'Boca Raton, FL',
  255. * 'ICBM' => '26.367559, -80.12172',
  256. * ),
  257. * 'lang' =>
  258. * array (
  259. * 'DC.title' => 'en',
  260. * ),
  261. * 'scheme' =>
  262. * array (
  263. * 'DCTERMS.modified' => 'XSD.date',
  264. * ),
  265. * </pre>
  266. *
  267. * Note how repeated tags cause the previous value to be overwritten in the resulting array
  268. * (for example the <var>Content-Type</var> and <var>keywords</var> tags appear twice but the
  269. * final array only has one element for each - the lowest one in the original list).
  270. *
  271. * @param string $html The string containing the html to be parsed
  272. */
  273. public static /*.array[string][string]string.*/ function meta_to_array(/*.string.*/ $html) {
  274. $keyAttributes = array('name', 'http-equiv', 'charset', 'itemprop');
  275. $tags = /*.(array[int][int]string).*/ array();
  276. $query = '?';
  277. preg_match_all("|<meta.+/$query>|i", $html, $tags);
  278. $meta = /*.(array[string][string]string).*/ array();
  279. $key_type = '';
  280. $key = '';
  281. $content = '';
  282. foreach ($tags[0] as $tag) {
  283. $attributes = array();
  284. $wip = /*.(array[string]string).*/ array();
  285. preg_match_all('|\\s(\\S+?)\\s*=\\s*"(.*?)"|', $tag, $attributes);
  286. unset($key_type);
  287. unset($key);
  288. unset($content);
  289. for ($i = 0; $i < count($attributes[1]); $i++) {
  290. $attribute = strtolower($attributes[1][$i]);
  291. $value = $attributes[2][$i];
  292. if (in_array($attribute, $keyAttributes)) {
  293. $key_type = $attribute;
  294. $key = $value;
  295. } elseif ($attribute === 'content') {
  296. $content = $value;
  297. } else {
  298. $wip[$attribute] = $value;
  299. }
  300. }
  301. if (isset($key_type)) {
  302. $meta['key'][$key] = $content;
  303. $meta[$key_type][$key] = $content;
  304. foreach ($wip as $attribute => $value) {
  305. $meta[$attribute][$key] = $value;
  306. }
  307. }
  308. }
  309. return $meta;
  310. }
  311. /**
  312. * Return the contents of a captured var_dump() as HTML. This is a recursive function.
  313. *
  314. * @param string $var_dump The captured <var>var_dump()</var>.
  315. * @param int $offset Whereabouts to start in the captured string. Defaults to the beginning of the string.
  316. */
  317. public static /*.string.*/ function var_dump_to_HTML(/*.string.*/ $var_dump, $offset = 0) {
  318. $indent = '';
  319. $value = '';
  320. while ((boolean) ($posStart = strpos($var_dump, '(', $offset))) {
  321. $type = substr($var_dump, $offset, $posStart - $offset);
  322. $nests = strrpos($type, ' ');
  323. if ($nests === false) $nests = 0; else $nests = intval(($nests + 1) / 2);
  324. $indent = str_pad('', $nests * 3, "\t");
  325. $type = trim($type);
  326. $offset = ++$posStart;
  327. $posEnd = strpos($var_dump, ')', $offset); if ($posEnd === false) break;
  328. $offset = $posEnd + 1;
  329. $value = substr($var_dump, $posStart, $posEnd - $posStart);
  330. switch ($type) {
  331. case 'string':
  332. $length = (int) $value;
  333. $value = '<pre>' . htmlspecialchars(substr($var_dump, $offset + 2, $length)) . '</pre>';
  334. $offset += $length + 3;
  335. break;
  336. case 'array':
  337. $elementTellTale = "\n" . str_pad('', ($nests + 1) * 2) . '['; // Not perfect but the best var_dump will allow
  338. $elementCount = (int) $value;
  339. $value = "\n$indent<table>\n";
  340. for ($i = 1; $i <= $elementCount; $i++) {
  341. $posStart = strpos($var_dump, $elementTellTale, $offset); if ($posStart === false) break;
  342. $posStart += ($nests + 1) * 2 + 2;
  343. $offset = $posStart;
  344. $posEnd = strpos($var_dump, ']', $offset); if ($posEnd === false) break;
  345. $offset = $posEnd + 4; // Read past the =>\n
  346. $key = substr($var_dump, $posStart, $posEnd - $posStart);
  347. if (!is_numeric($key)) $key = substr($key, 1, strlen($key) - 2); // Strip off the double quotes
  348. $search = ($i === $elementCount) ? "\n" . str_pad('', $nests * 2) . '}' : $elementTellTale;
  349. $posStart = strpos($var_dump, $search, $offset); if ($posStart === false) break;
  350. $next = substr($var_dump, $offset, $posStart - $offset);
  351. $offset = $posStart;
  352. $inner_value = self::var_dump_to_HTML($next);
  353. $value .= "$indent\t<tr>\n";
  354. $value .= "$indent\t\t<td>$key</td>\n";
  355. $value .= "$indent\t\t<td>$inner_value</td>\n";
  356. $value .= "$indent\t</tr>\n";
  357. }
  358. $value .= "$indent</table>\n";
  359. break;
  360. case 'object':
  361. if ($value === '__PHP_Incomplete_Class') {
  362. $posStart = strpos($var_dump, '(', $offset); if ($posStart === false) break;
  363. $offset = ++$posStart;
  364. echo "$indent Corrected \$offset = $offset\n"; // debug
  365. $posEnd = strpos($var_dump, ')', $offset); if ($posEnd === false) break;
  366. $offset = $posEnd + 1;
  367. echo "$indent Corrected \$offset = $offset\n"; // debug
  368. $value = substr($var_dump, $posStart, $posEnd - $posStart);
  369. }
  370. break;
  371. default:
  372. break;
  373. }
  374. }
  375. return $value;
  376. }
  377. /**
  378. * Return the contents of an array as HTML (like <var>var_dump()</var> on steroids), including object members
  379. *
  380. * @param mixed $source The array to export. If it's empty then $GLOBALS is exported.
  381. */
  382. public static /*.string.*/ function array_to_HTML(/*.array[]mixed.*/ $source = NULL) {
  383. // If no specific array is passed we will export $GLOBALS to HTML
  384. // Unfortunately, this means we have to use var_dump() because var_export() barfs on $GLOBALS
  385. // In fact var_dump is easier to walk than var_export anyway so this is no bad thing.
  386. ob_start();
  387. if (empty($source)) var_dump($GLOBALS); else var_dump($source);
  388. $var_dump = ob_get_clean();
  389. return self::var_dump_to_HTML($var_dump);
  390. }
  391. /**
  392. * Check session is running. If not start one.
  393. */
  394. public static /*.void.*/ function checkSession() {if (!isset($_SESSION) || !is_array($_SESSION) || (session_id() === '')) session_start();}
  395. ///**
  396. // * Return the name of this package. By default this will be in lower case for use in Javascript tags etc.
  397. // *
  398. // * @param int $mode One of the <var>PACKAGE_CASE_XXX</var> predefined constants defined in this class
  399. // */
  400. // public static /*.string.*/ function getPackage($mode = self::PACKAGE_CASE_DEFAULT) {
  401. // switch ($mode) {
  402. // case self::PACKAGE_CASE_CAMEL:
  403. // $package = self::PACKAGE;
  404. // break;
  405. // case self::PACKAGE_CASE_UPPER:
  406. // $package = strtoupper(self::PACKAGE);
  407. // break;
  408. // default:
  409. // $package = strtolower(self::PACKAGE);
  410. // break;
  411. // }
  412. //
  413. // return $package;
  414. // }
  415. /**
  416. * Return all or part of the URL of the current script.
  417. *
  418. * @param int $mode One of the <var>URL_MODE_XXX</var> predefined constants defined in this class
  419. * @param string $filename If this is not empty then the returned script name is forced to be this filename.
  420. */
  421. public static /*.string.*/ function getURL($mode = self::URL_MODE_PATH, $filename = 'ezUser') {
  422. // Version 1.14: PACKAGE & VERSION now hard-coded by build process.
  423. $portInteger = array_key_exists('SERVER_PORT', $_SERVER) ? (int) $_SERVER['SERVER_PORT'] : 0;
  424. if (array_key_exists('HTTPS', $_SERVER) && $_SERVER['HTTPS'] === 'on') {
  425. $protocolType = 'https';
  426. } else if (array_key_exists('SERVER_PROTOCOL', $_SERVER)) {
  427. $protocolType = strtolower(self::strleft($_SERVER['SERVER_PROTOCOL'], self::URL_SEPARATOR, self::STRLEFT_MODE_ALL));
  428. } else if ($portInteger === 443) {
  429. $protocolType = 'https';
  430. } else {
  431. $protocolType = 'http';
  432. }
  433. if ($portInteger === 0) $portInteger = ($protocolType === 'https') ? 443 : 80;
  434. // Protocol
  435. if ((boolean) ($mode & self::URL_MODE_PROTOCOL)) {
  436. $protocol = ($mode === self::URL_MODE_PROTOCOL) ? $protocolType : "$protocolType://";
  437. } else {
  438. $protocol = '';
  439. }
  440. // Host
  441. if ((boolean) ($mode & self::URL_MODE_HOST)) {
  442. $host = array_key_exists('HTTP_HOST', $_SERVER) ? self::strleft($_SERVER['HTTP_HOST'], ':', self::STRLEFT_MODE_ALL) : '';
  443. } else {
  444. $host = '';
  445. }
  446. // Port
  447. if ((boolean) ($mode & self::URL_MODE_PORT)) {
  448. $port = (string) $portInteger;
  449. if ($mode !== self::URL_MODE_PORT)
  450. $port = (($protocolType === 'http' && $portInteger === 80) || ($protocolType === 'https' && $portInteger === 443)) ? '' : ":$port";
  451. } else {
  452. $port = '';
  453. }
  454. // Path
  455. if ((boolean) ($mode & self::URL_MODE_PATH)) {
  456. $includePath = __FILE__;
  457. $scriptPath = realpath($_SERVER['SCRIPT_FILENAME']);
  458. if (DIRECTORY_SEPARATOR !== self::URL_SEPARATOR) {
  459. $includePath = (string) str_replace(DIRECTORY_SEPARATOR, self::URL_SEPARATOR , $includePath);
  460. $scriptPath = (string) str_replace(DIRECTORY_SEPARATOR, self::URL_SEPARATOR , $scriptPath);
  461. }
  462. /*
  463. echo "<pre>\n"; // debug
  464. echo "\$_SERVER['SCRIPT_FILENAME'] = " . $_SERVER['SCRIPT_FILENAME'] . "\n"; // debug
  465. echo "\$_SERVER['SCRIPT_NAME'] = " . $_SERVER['SCRIPT_NAME'] . "\n"; // debug
  466. echo "dirname(\$_SERVER['SCRIPT_NAME']) = " . dirname($_SERVER['SCRIPT_NAME']) . "\n"; // debug
  467. echo "\$includePath = $includePath\n"; // debug
  468. echo "\$scriptPath = $scriptPath\n"; // debug
  469. //echo self::array_to_HTML(); // debug
  470. echo "</pre>\n"; // debug
  471. */
  472. $start = strpos(strtolower($scriptPath), strtolower($_SERVER['SCRIPT_NAME']));
  473. $path = ($start === false) ? dirname($_SERVER['SCRIPT_NAME']) : dirname(substr($includePath, $start));
  474. $path .= self::URL_SEPARATOR . $filename;
  475. } else {
  476. $path = '';
  477. }
  478. return $protocol . $host . $port . $path;
  479. }
  480. /**
  481. * Convert a DocBlock to HTML (see http://java.sun.com/j2se/javadoc/writingdoccomments/index.html)
  482. *
  483. * @param string $docBlock Some PHP code containing a valid DocBlock.
  484. */
  485. public static /*.string.*/ function docBlock_to_HTML(/*.string.*/ $php) {
  486. // Updated in version 1.12 (bug fixes and formatting)
  487. // $package = self::getPackage(self::PACKAGE_CASE_CAMEL); // Version 1.14: PACKAGE & VERSION now hard-coded by build process.
  488. $eol = "\r\n";
  489. $tagStart = strpos($php, "/**$eol * ");
  490. if ($tagStart === false) return 'Development version';
  491. // Get summary and long description
  492. $tagStart += 8;
  493. $tagEnd = strpos($php, $eol, $tagStart);
  494. $summary = substr($php, $tagStart, $tagEnd - $tagStart);
  495. $tagStart = $tagEnd + 7;
  496. $tagPos = strpos($php, "$eol * @") + 2;
  497. $description = substr($php, $tagStart, $tagPos - $tagStart - 7);
  498. $description = (string) str_replace(' * ', '' , $description);
  499. // Get tags and values from DocBlock
  500. do {
  501. $tagStart = $tagPos + 4;
  502. $tagEnd = strpos($php, "\t", $tagStart);
  503. $tag = substr($php, $tagStart, $tagEnd - $tagStart);
  504. $offset = $tagEnd + 1;
  505. $tagPos = strpos($php, $eol, $offset);
  506. $value = htmlspecialchars(substr($php, $tagEnd + 1, $tagPos - $tagEnd - 1));
  507. $tagPos = strpos($php, " * @", $offset);
  508. // $$tag = htmlspecialchars($value); // The easy way. But PHPlint doesn't like it, so...
  509. // $package = '';
  510. // $summary = '';
  511. // $description = '';
  512. switch ($tag) {
  513. case 'license': $license = $value; break;
  514. case 'author': $author = $value; break;
  515. case 'link': $link = $value; break;
  516. case 'version': $version = $value; break;
  517. case 'copyright': $copyright = $value; break;
  518. default: $value = $value;
  519. }
  520. } while ((boolean) $tagPos);
  521. // Add some links
  522. // 1. License
  523. if (isset($license) && (boolean) strpos($license, '://')) {
  524. $tagPos = strpos($license, ' ');
  525. $license = '<a href="' . substr($license, 0, $tagPos) . '">' . substr($license, $tagPos + 1) . '</a>';
  526. }
  527. // 2. Author
  528. if (isset($author) && preg_match('/&lt;.+@.+&gt;/', $author) > 0) {
  529. $tagStart = strpos($author, '&lt;') + 4;
  530. $tagEnd = strpos($author, '&gt;', $tagStart);
  531. $author = '<a href="mailto:' . substr($author, $tagStart, $tagEnd - $tagStart) . '">' . substr($author, 0, $tagStart - 5) . '</a>';
  532. }
  533. // 3. Link
  534. if (isset($link) && (boolean) strpos($link, '://')) {
  535. $link = '<a href="' . $link . '">' . $link . '</a>';
  536. }
  537. // Build the HTML
  538. $html = <<<HTML
  539. <h1>ezUser</h1>
  540. <h2>$summary</h2>
  541. <pre>$description</pre>
  542. <hr />
  543. <table>
  544. HTML;
  545. // Version 1.14: PACKAGE & VERSION now hard-coded by build process.
  546. if (isset($version)) $html .= "\t\t<tr><td>Version</td><td>$version</td></tr>\n";
  547. if (isset($copyright)) $html .= "\t\t<tr><td>Copyright</td><td>$copyright</td></tr>\n";
  548. if (isset($license)) $html .= "\t\t<tr><td>License</td><td>$license</td></tr>\n";
  549. if (isset($author)) $html .= "\t\t<tr><td>Author</td><td>$author</td></tr>\n";
  550. if (isset($link)) $html .= "\t\t<tr><td>Link</td><td>$link</td></tr>\n";
  551. $html .= "\t</table>";
  552. return $html;
  553. }
  554. /**
  555. * glob() replacement (in case glob() is disabled).
  556. *
  557. * Function glob() is prohibited on some server (probably in safe mode)
  558. * (Message "Warning: glob() has been disabled for security reasons in
  559. * (script) on line (line)") for security reasons as stated on:
  560. * http://seclists.org/fulldisclosure/2005/Sep/0001.html
  561. *
  562. * safe_glob() intends to replace glob() using readdir() & fnmatch() instead.
  563. * Supported flags: GLOB_MARK, GLOB_NOSORT, GLOB_ONLYDIR
  564. * Additional flags: GLOB_NODIR, GLOB_PATH, GLOB_NODOTS, GLOB_RECURSE
  565. * (these were not original glob() flags)
  566. * @author BigueNique AT yahoo DOT ca
  567. */
  568. public static /*.mixed.*/ function safe_glob(/*.string.*/ $pattern, /*.int.*/ $flags = 0) {
  569. $split = explode('/', (string) str_replace('\\', '/', $pattern));
  570. $mask = (string) array_pop($split);
  571. $path = (count($split) === 0) ? '.' : implode('/', $split);
  572. $dir = @opendir($path);
  573. if ($dir === false) return false;
  574. $glob = /*.(array[int]).*/ array();
  575. do {
  576. $filename = readdir($dir);
  577. if ($filename === false) break;
  578. $is_dir = is_dir("$path/$filename");
  579. $is_dot = in_array($filename, array('.', '..'));
  580. // Recurse subdirectories (if GLOB_RECURSE is supplied)
  581. if ($is_dir && !$is_dot && (($flags & self::GLOB_RECURSE) !== 0)) {
  582. $sub_glob = /*.(array[int]).*/ self::safe_glob($path.'/'.$filename.'/'.$mask, $flags);
  583. // array_prepend($sub_glob, ((boolean) ($flags & self::GLOB_PATH) ? '' : $filename.'/'));
  584. $glob = /*.(array[int]).*/ array_merge($glob, $sub_glob);
  585. }
  586. // Match file mask
  587. if (fnmatch($mask, $filename)) {
  588. if ( ((($flags & GLOB_ONLYDIR) === 0) || $is_dir)
  589. && ((($flags & self::GLOB_NODIR) === 0) || !$is_dir)
  590. && ((($flags & self::GLOB_NODOTS) === 0) || !$is_dot)
  591. )
  592. $glob[] = (($flags & self::GLOB_PATH) !== 0 ? $path.'/' : '') . $filename . (($flags & GLOB_MARK) !== 0 ? '/' : '');
  593. }
  594. } while(true);
  595. closedir($dir);
  596. if (($flags & GLOB_NOSORT) === 0) sort($glob);
  597. return $glob;
  598. }
  599. /**
  600. * Return file contents as a string. Fail silently if the file can't be opened.
  601. *
  602. * The parameters are the same as the built-in PHP function {@link http://www.php.net/file_get_contents file_get_contents}
  603. */
  604. public static /*.string.*/ function getFileContents(/*.string.*/ $filename, /*.int.*/ $flags = 0, /*.object.*/ $context = NULL, /*.int.*/ $offset = -1, /*.int.*/ $maxlen = -1) {
  605. // From the documentation of file_get_contents:
  606. // Note: The default value of maxlen is not actually -1; rather, it is an internal PHP value which means to copy the entire stream until end-of-file is reached. The only way to specify this default value is to leave it out of the parameter list.
  607. if ($maxlen === -1) {
  608. $contents = @file_get_contents($filename, $flags, $context, $offset);
  609. } else {
  610. $contents = @file_get_contents($filename, $flags, $context, $offset, $maxlen);
  611. // version 1.9 - remembered the @s
  612. }
  613. if ($contents === false) $contents = '';
  614. return $contents;
  615. }
  616. /**
  617. * Return the name of the index file (e.g. <var>index.php</var>) from a folder
  618. *
  619. * @param string $folder The folder to look for the index file. If not a folder or no index file can be found then an empty string is returned.
  620. */
  621. public static /*.string.*/ function findIndexFile(/*.string.*/ $folder) {
  622. if (!is_dir($folder)) return '';
  623. $filelist = array('index.php', 'index.pl', 'index.cgi', 'index.asp', 'index.shtml', 'index.html', 'index.htm', 'default.php', 'default.pl', 'default.cgi', 'default.asp', 'default.shtml', 'default.html', 'default.htm', 'home.php', 'home.pl', 'home.cgi', 'home.asp', 'home.shtml', 'home.html', 'home.htm');
  624. foreach ($filelist as $filename) {
  625. $target = $folder . DIRECTORY_SEPARATOR . $filename;
  626. if (is_file($target)) return $target;
  627. }
  628. return '';
  629. }
  630. /**
  631. * Return the name of the target file from a string that might be a directory or just a basename without a suffix. If it's a directory then look for an index file in the directory.
  632. *
  633. * @param string $target The file to look for or folder to look in. If no file can be found then an empty string is returned.
  634. */
  635. public static /*.string.*/ function findTarget(/*.string.*/ $target) {
  636. // Is it actually a file? If so, look no further
  637. if (is_file($target)) return $target;
  638. // Added in version 1.7
  639. // Is it a basename? i.e. can we find $target.html or something?
  640. $suffixes = array('shtml', 'html', 'php', 'pl', 'cgi', 'asp', 'htm');
  641. foreach ($suffixes as $suffix) {
  642. $filename = "$target.$suffix";
  643. if (is_file($filename)) return $filename;
  644. }
  645. // Otherwise, let's assume it's a directory and try to find an index file in that directory
  646. return self::findIndexFile($target);
  647. }
  648. /**
  649. * Make a unique ID based on the current date and time
  650. */
  651. public static /*.string.*/ function makeId() {
  652. // Note could also try this: return md5(uniqid(mt_rand(), true));
  653. list($usec, $sec) = explode(" ", (string) microtime());
  654. return base_convert($sec, 10, 36) . base_convert((string) mt_rand(0, 35), 10, 36) . str_pad(base_convert(($usec * 1000000), 10, 36), 4, '_', STR_PAD_LEFT);
  655. }
  656. /**
  657. * Make a unique hash key from a string (usually an ID)
  658. */
  659. public static /*.string.*/ function makeUniqueKey(/*.string.*/ $id) {
  660. return hash(self::HASH_FUNCTION, $_SERVER['REQUEST_TIME'] . $id);
  661. }
  662. // Added in version 1.10
  663. /**
  664. * Shuffle a string using the Mersenne Twist PRNG (can be deterministically seeded)
  665. *
  666. * @param string $str The string to be shuffled
  667. * @param int $seed The seed for the PRNG means this can be used to shuffle the string in the same order every time
  668. */
  669. public static /*.string.*/ function mt_shuffle(/*.string.*/ $str, /*.int.*/ $seed = 0) {
  670. $count = strlen($str);
  671. $result = $str;
  672. // Seed the RNG with a deterministic seed
  673. mt_srand($seed);
  674. // Shuffle the digits
  675. for ($element = $count - 1; $element >= 0; $element--) {
  676. $shuffle = mt_rand(0, $element);
  677. $value = $result[$shuffle];
  678. // $result[$shuffle] = $result[$element];
  679. // $result[$element] = $value; // PHPLint doesn't like this syntax, so...
  680. substr_replace($result, $result[$element], $shuffle, 1);
  681. substr_replace($result, $value, $element, 1);
  682. }
  683. return $result;
  684. }
  685. // Added in version 1.10
  686. /**
  687. * Shuffle an array using the Mersenne Twist PRNG (can be deterministically seeded)
  688. *
  689. */
  690. public static /*.void.*/ function mt_shuffle_array(/*.array.*/ &$arr, /*.int.*/ $seed = 0) {
  691. $count = count($arr);
  692. $keys = array_keys($arr);
  693. // Seed the RNG with a deterministic seed
  694. mt_srand($seed);
  695. // Shuffle the digits
  696. for ($element = $count - 1; $element >= 0; $element--) {
  697. $shuffle = mt_rand(0, $element);
  698. $key_shuffle = $keys[$shuffle];
  699. $key_element = $keys[$element];
  700. $value = $arr[$key_shuffle];
  701. $arr[$key_shuffle] = $arr[$key_element];
  702. $arr[$key_element] = $value;
  703. }
  704. }
  705. // Added in version 1.10
  706. /**
  707. * The Pseudo-Random Key Generator returns an apparently random key of
  708. * length $length and comprising digits specified by $base. However, for
  709. * a given seed this key depends only on $index.
  710. *
  711. * In other words, if you keep the $seed constant then you'll get a
  712. * non-repeating series of keys as you increment $index but these keys
  713. * will be returned in a pseudo-random order.
  714. *
  715. * The $seed parameter is available in case you want your series of keys
  716. * to come out in a different order to mine.
  717. *
  718. * Comparison of bases:
  719. * <pre>
  720. * +------+----------------+---------------------------------------------+
  721. * | | Max keys | |
  722. * | | (based on | |
  723. * | Base | $length = 6) | Notes |
  724. * +------+----------------+---------------------------------------------+
  725. * | 2 | 64 | Uses digits 0 and 1 only |
  726. * | 8 | 262,144 | Uses digits 0-7 only |
  727. * | 10 | 1,000,000 | Good choice if you need integer keys |
  728. * | 16 | 16,777,216 | Good choice if you need hex keys |
  729. * | 26 | 308,915,776 | Good choice if you need purely alphabetic |
  730. * | | | keys (case-insensitive) |
  731. * | 32 | 1,073,741,824 | Smallest base that gives you a billion keys |
  732. * | | | in 6 digits |
  733. * | 34 | 1,544,804,416 | (default) Good choice if you want to |
  734. * | | | maximise your keyset size but still |
  735. * | | | generate keys that are unambiguous and |
  736. * | | | case-insensitive (no confusion between 1, I |
  737. * | | | and l for instance) |
  738. * | 36 | 2,176,782,336 | Same digits as base-34 but includes 'O' and |
  739. * | | | 'I' (may be confused with '0' and '1' in |
  740. * | | | some fonts) |
  741. * | 52 | 19,770,609,664 | Good choice if you need purely alphabetic |
  742. * | | | keys (case-sensitive) |
  743. * | 62 | 56,800,235,584 | Same digits as other URL shorteners |
  744. * | | | (e.g bit.ly) |
  745. * | 66 | 82,653,950,016 | Includes all legal URI characters |
  746. * | | | (http://tools.ietf.org/html/rfc3986) |
  747. * | | | This is the maximum size of keyset that |
  748. * | | | results in a legal URL for a given length |
  749. * | | | of key. |
  750. * +------+----------------+---------------------------------------------+
  751. * </pre>
  752. * @param int $index The number to be converted into a key
  753. * @param int $length The length of key to be returned. Along with the $base this determines the size of the keyset
  754. * @param int $base The number of distinct characters that can be included in the key to be returned. Along with the $length this determines the size of the keyset
  755. * @param int $seed The seed for the PRNG means this can be used to generate keys in the same sequence every time
  756. */
  757. public static /*.string.*/ function prkg($index, $length = 6, $base = 34, $seed = 0) {
  758. /*
  759. To return a pseudo-random key, we will take $index, convert it
  760. to base $base, then randomize the order of the digits. In
  761. addition we will give each digit a random offset.
  762. All the randomization operations are deterministic (based on
  763. $seed) so each time the function is called we will get the
  764. same shuffling of digits and the same offset for each digit.
  765. */
  766. $digits = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZIOabcdefghijklmnopqrstuvwxyz-._~';
  767. // ^ base 34 recommended
  768. // Is $base in range?
  769. if ($base < 2) {die('Base must be greater than or equal to 2');}
  770. if ($base > 66) {die('Base must be less than or equal to 66');}
  771. // Is $length in range?
  772. if ($length < 1) {die('Length must be greater than or equal to 1');}
  773. // Max length depends on arithmetic functions of PHP
  774. // Is $index in range?
  775. $max_index = (int) pow($base, $length);
  776. if ($index < 0) {die('Index must be greater than or equal to 0');}
  777. if ($index > $max_index) {die('Index must be less than or equal to ' . $max_index);}
  778. // Seed the RNG with a deterministic seed
  779. mt_srand($seed);
  780. // Convert to $base
  781. $remainder = $index;
  782. $digit = 0;
  783. $result = '';
  784. while ($digit < $length) {
  785. $unit = (int) pow($base, $length - $digit++ - 1);
  786. $value = (int) floor($remainder / $unit);
  787. $remainder = $remainder - ($value * $unit);
  788. // Shift the digit
  789. $value = ($value + mt_rand(0, $base - 1)) % $base;
  790. $result .= $digits[$value];
  791. }
  792. // Shuffle the digits
  793. $result = self::mt_shuffle($result, $seed);
  794. // We're done
  795. return $result;
  796. }
  797. // Updated in version 1.8
  798. /**
  799. * Check that an email address conforms to RFC5322 and other RFCs
  800. *
  801. * @param boolean $checkDNS If true then a DNS check for A and MX records will be made
  802. * @param boolean $diagnose If true then return an integer error number rather than true or false
  803. */
  804. public static /*.mixed.*/ function is_email (/*.string.*/ $email, $checkDNS = false, $diagnose = false) {
  805. // Check that $email is a valid address. Read the following RFCs to understand the constraints:
  806. // (http://tools.ietf.org/html/rfc5322)
  807. // (http://tools.ietf.org/html/rfc3696)
  808. // (http://tools.ietf.org/html/rfc5321)
  809. // (http://tools.ietf.org/html/rfc4291#section-2.2)
  810. // (http://tools.ietf.org/html/rfc1123#section-2.1)
  811. // the upper limit on address lengths should normally be considered to be 256
  812. // (http://www.rfc-editor.org/errata_search.php?rfc=3696)
  813. // NB I think John Klensin is misreading RFC 5321 and the the limit should actually be 254
  814. // However, I will stick to the published number until it is changed.
  815. //
  816. // The maximum total length of a reverse-path or forward-path is 256
  817. // characters (including the punctuation and element separators)
  818. // (http://tools.ietf.org/html/rfc5321#section-4.5.3.1.3)
  819. $emailLength = strlen($email);
  820. if ($emailLength > 256) if ($diagnose) return self::ISEMAIL_TOOLONG; else return false; // Too long
  821. // Contemporary email addresses consist of a "local part" separated from
  822. // a "domain part" (a fully-qualified domain name) by an at-sign ("@").
  823. // (http://tools.ietf.org/html/rfc3696#section-3)
  824. $atIndex = strrpos($email,'@');
  825. if ($atIndex === false) if ($diagnose) return self::ISEMAIL_NOAT; else return false; // No at-sign
  826. if ($atIndex === 0) if ($diagnose) return self::ISEMAIL_NOLOCALPART; else return false; // No local part
  827. if ($atIndex === $emailLength - 1) if ($diagnose) return self::ISEMAIL_NODOMAIN; else return false; // No domain part
  828. // revision 1.14: Length test bug suggested by Andrew Campbell of Gloucester, MA
  829. // Sanitize comments
  830. // - remove nested comments, quotes and dots in comments
  831. // - remove parentheses and dots from quoted strings
  832. $braceDepth = 0;
  833. $inQuote = false;
  834. $escapeThisChar = false;
  835. for ($i = 0; $i < $emailLength; ++$i) {
  836. $char = $email[$i];
  837. $replaceChar = false;
  838. if ($char === '\\') {
  839. $escapeThisChar = !$escapeThisChar; // Escape the next character?
  840. } else {
  841. switch ($char) {
  842. case '(':
  843. if ($escapeThisChar) {
  844. $replaceChar = true;
  845. } else {
  846. if ($inQuote) {
  847. $replaceChar = true;
  848. } else {
  849. if ($braceDepth++ > 0) $replaceChar = true; // Increment brace depth
  850. }
  851. }
  852. break;
  853. case ')':
  854. if ($escapeThisChar) {
  855. $replaceChar = true;
  856. } else {
  857. if ($inQuote) {
  858. $replaceChar = true;
  859. } else {
  860. if (--$braceDepth > 0) $replaceChar = true; // Decrement brace depth
  861. if ($braceDepth < 0) $braceDepth = 0;
  862. }
  863. }
  864. break;
  865. case '"':
  866. if ($escapeThisChar) {
  867. $replaceChar = true;
  868. } else {
  869. if ($braceDepth === 0) {
  870. $inQuote = !$inQuote; // Are we inside a quoted string?
  871. } else {
  872. $replaceChar = true;
  873. }
  874. }
  875. break;
  876. case '.': // Dots don't help us either
  877. if ($escapeThisChar) {
  878. $replaceChar = true;
  879. } else {
  880. if ($braceDepth > 0) $replaceChar = true;
  881. }
  882. break;
  883. default:
  884. }
  885. $escapeThisChar = false;
  886. // if ($replaceChar) $email[$i] = 'x'; // Replace the offending character with something harmless
  887. // revision 1.12: Line above replaced because PHPLint doesn't like that syntax
  888. if ($replaceChar) $email = (string) substr_replace($email, 'x', $i, 1); // Replace the offending character with something harmless
  889. }
  890. }
  891. $localPart = substr($email, 0, $atIndex);
  892. $domain = substr($email, $atIndex + 1);
  893. $FWS = "(?:(?:(?:[ \\t]*(?:\\r\\n))?[ \\t]+)|(?:[ \\t]+(?:(?:\\r\\n)[ \\t]+)*))"; // Folding white space
  894. // Let's check the local part for RFC compliance...
  895. //
  896. // local-part = dot-atom / quoted-string / obs-local-part
  897. // obs-local-part = word *("." word)
  898. // (http://tools.ietf.org/html/rfc5322#section-3.4.1)
  899. //
  900. // Problem: need to distinguish between "first.last" and "first"."last"
  901. // (i.e. one element or two). And I suck at regexes.
  902. $dotArray = /*. (array[int]string) .*/ preg_split('/\\.(?=(?:[^\\"]*\\"[^\\"]*\\")*(?![^\\"]*\\"))/m', $localPart);
  903. $partLength = 0;
  904. foreach ($dotArray as $element) {
  905. // Remove any leading or trailing FWS
  906. $element = preg_replace("/^$FWS|$FWS\$/", '', $element);
  907. $elementLength = strlen($element);
  908. if ($elementLength === 0) if ($diagnose) return self::ISEMAIL_ZEROLENGTHELEMENT; else return false; // Can't have empty element (consecutive dots or dots at the start or end)
  909. // revision 1.15: Speed up the test and get rid of "unitialized string offset" notices from PHP
  910. // We need to remove any valid comments (i.e. those at the start or end of the element)
  911. if ($element[0] === '(') {
  912. $indexBrace = strpos($element, ')');
  913. if ($indexBrace !== false) {
  914. if (preg_match('/(?<!\\\\)[\\(\\)]/', substr($element, 1, $indexBrace - 1)) > 0) {
  915. if ($diagnose) return self::ISEMAIL_BADCOMMENT_START; else return false; // Illegal characters in comment
  916. }
  917. $element = substr($element, $indexBrace + 1, $elementLength - $indexBrace - 1);
  918. $elementLength = strlen($element);
  919. }
  920. }
  921. if ($element[$elementLength - 1] === ')') {
  922. $indexBrace = strrpos($element, '(');
  923. if ($indexBrace !== false) {
  924. if (preg_match('/(?<!\\\\)(?:[\\(\\)])/', substr($element, $indexBrace + 1, $elementLength - $indexBrace - 2)) > 0) {
  925. if ($diagnose) return self::ISEMAIL_BADCOMMENT_END; else return false; // Illegal characters in comment
  926. }
  927. $element = substr($element, 0, $indexBrace);
  928. $elementLength = strlen($element);
  929. }
  930. }
  931. // Remove any leading or trailing FWS around the element (inside any comments)
  932. $element = preg_replace("/^$FWS|$FWS\$/", '', $element);
  933. // What's left counts towards the maximum length for this part
  934. if ($partLength > 0) $partLength++; // for the dot
  935. $partLength += strlen($element);
  936. // Each dot-delimited component can be an atom or a quoted string
  937. // (because of the obs-local-part provision)
  938. if (preg_match('/^"(?:.)*"$/s', $element) > 0) {
  939. // Quoted-string tests:
  940. //
  941. // Remove any FWS
  942. $element = preg_replace("/(?<!\\\\)$FWS/", '', $element);
  943. // My regex skillz aren't up to distinguishing between \" \\" \\\" \\\\" etc.
  944. // So remove all \\ from the string first...
  945. $element = preg_replace('/\\\\\\\\/', ' ', $element);
  946. if (preg_match('/(?<!\\\\|^)["\\r\\n\\x00](?!$)|\\\\"$|""/', $element) > 0) if ($diagnose) return self::ISEMAIL_UNESCAPEDDELIM; else return false; // ", CR, LF and NUL must be escaped, "" is too short
  947. } else {
  948. // Unquoted string tests:
  949. //
  950. // Period (".") may...appear, but may not be used to start or end the
  951. // local part, nor may two or more consecutive periods appear.
  952. // (http://tools.ietf.org/html/rfc3696#section-3)
  953. //
  954. // A zero-length element implies a period at the beginning or end of the
  955. // local part, or two periods together. Either way it's not allowed.
  956. if ($element === '') if ($diagnose) return self::ISEMAIL_EMPTYELEMENT; else return false; // Dots in wrong place
  957. // Any ASCII graphic (printing) character other than the
  958. // at-sign ("@"), backslash, double quote, comma, or square brackets may
  959. // appear without quoting. If any of that list of excluded characters
  960. // are to appear, they must be quoted
  961. // (http://tools.ietf.org/html/rfc3696#section-3)
  962. //
  963. // Any excluded characters? i.e. 0x00-0x20, (, ), <, >, [, ], :, ;, @, \, comma, period, "
  964. if (preg_match('/[\\x00-\\x20\\(\\)<>\\[\\]:;@\\\\,\\."]/', $element) > 0) if ($diagnose) return self::ISEMAIL_UNESCAPEDSPECIAL; else return false; // These characters must be in a quoted string
  965. }
  966. }
  967. if ($partLength > 64) if ($diagnose) return self::ISEMAIL_LOCALTOOLONG; else return false; // Local part must be 64 characters or less
  968. // Now let's check the domain part...
  969. // The domain name can also be replaced by an IP address in square brackets
  970. // (http://tools.ietf.org/html/rfc3696#section-3)
  971. // (http://tools.ietf.org/html/rfc5321#section-4.1.3)
  972. // (http://tools.ietf.org/html/rfc4291#section-2.2)
  973. if (preg_match('/^\\[(.)+]$/', $domain) === 1) {
  974. // It's an address-literal
  975. $addressLiteral = substr($domain, 1, strlen($domain) - 2);
  976. $matchesIP = array();
  977. // Extract IPv4 part from the end of the address-literal (if there is one)
  978. if (preg_match('/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/', $addressLiteral, $matchesIP) > 0) {
  979. $index = strrpos($addressLiteral, $matchesIP[0]);
  980. if ($index === 0) {
  981. // Nothing there except a valid IPv4 address, so...
  982. if ($diagnose) return self::ISEMAIL_VALID; else return true;
  983. } else {
  984. // Assume it's an attempt at a mixed address (IPv6 + IPv4)
  985. if ($addressLiteral[$index - 1] !== ':') if ($diagnose) return self::ISEMAIL_IPV4BADPREFIX; else return false; // Character preceding IPv4 address must be ':'
  986. if (substr($addressLiteral, 0, 5) !== 'IPv6:') if ($diagnose) return self::ISEMAIL_IPV6BADPREFIXMIXED; else return false; // RFC5321 section 4.1.3
  987. $IPv6 = substr($addressLiteral, 5, ($index ===7) ? 2 : $index - 6);
  988. $groupMax = 6;
  989. }
  990. } else {
  991. // It must be an attempt at pure IPv6
  992. if (substr($addressLiteral, 0, 5) !== 'IPv6:') if ($diagnose) return self::ISEMAIL_IPV6BADPREFIX; else return false; // RFC5321 section 4.1.3
  993. $IPv6 = substr($addressLiteral, 5);
  994. $groupMax = 8;
  995. }
  996. $groupCount = preg_match_all('/^[0-9a-fA-F]{0,4}|\\:[0-9a-fA-F]{0,4}|(.)/', $IPv6, $matchesIP);
  997. $index = strpos($IPv6,'::');
  998. if ($index === false) {
  999. // We need exactly the right number of groups
  1000. if ($groupCount !== $groupMax) if ($diagnose) return self::ISEMAIL_IPV6GROUPCOUNT; else return false; // RFC5321 section 4.1.3
  1001. } else {
  1002. if ($index !== strrpos($IPv6,'::')) if ($diagnose) return self::ISEMAIL_IPV6DOUBLEDOUBLECOLON; else return false; // More than one '::'
  1003. $groupMax = ($index === 0 || $index === (strlen($IPv6) - 2)) ? $groupMax : $groupMax - 1;
  1004. if ($groupCount > $groupMax) if ($diagnose) return self::ISEMAIL_IPV6TOOMANYGROUPS; else return false; // Too many IPv6 groups in address
  1005. }
  1006. // Check for unmatched characters
  1007. array_multisort($matchesIP[1], SORT_DESC);
  1008. if ($matchesIP[1][0] !== '') if ($diagnose) return self::ISEMAIL_IPV6BADCHAR; else return false; // Illegal characters in address
  1009. // It's a valid IPv6 address, so...
  1010. if ($diagnose) return self::ISEMAIL_VALID; else return true;
  1011. } else {
  1012. // It's a domain name...
  1013. // The syntax of a legal Internet host name was specified in RFC-952
  1014. // One aspect of host name syntax is hereby changed: the
  1015. // restriction on the first character is relaxed to allow either a
  1016. // letter or a digit.
  1017. // (http://tools.ietf.org/html/rfc1123#section-2.1)
  1018. //
  1019. // NB RFC 1123 updates RFC 1035, but this is not currently apparent from reading RFC 1035.
  1020. //
  1021. // Most common applications, including email and the Web, will generally not
  1022. // permit...escaped strings
  1023. // (http://tools.ietf.org/html/rfc3696#section-2)
  1024. //
  1025. // the better strategy has now become to make the "at least one period" test,
  1026. // to verify LDH conformance (including verification that the apparent TLD name
  1027. // is not all-numeric)
  1028. // (http://tools.ietf.org/html/rfc3696#section-2)
  1029. //
  1030. // Characters outside the set of alphabetic characters, digits, and hyphen MUST NOT appear in domain name
  1031. // labels for SMTP clients or servers
  1032. // (http://tools.ietf.org/html/rfc5321#section-4.1.2)
  1033. //
  1034. // RFC5321 precludes the use of a trailing dot in a domain name for SMTP purposes
  1035. // (http://tools.ietf.org/html/rfc5321#section-4.1.2)
  1036. $dotArray = /*. (array[int]string) .*/ preg_split('/\\.(?=(?:[^\\"]*\\"[^\\"]*\\")*(?![^\\"]*\\"))/m', $domain);
  1037. $partLength = 0;
  1038. $element = ''; // Since we use $element after the foreach loop let's make sure it has a value
  1039. // revision 1.13: Line above added because PHPLint now checks for Definitely Assigned Variables
  1040. if (count($dotArray) === 1) if ($diagnose) return self::ISEMAIL_TLD; else return false; // Mail host can't be a TLD (cite? What about localhost?)
  1041. foreach ($dotArray as $element) {
  1042. // Remove any leading or trailing FWS
  1043. $element = preg_replace("/^$FWS|$FWS\$/", '', $element);
  1044. $elementLength = strlen($element);
  1045. // Each dot-delimited component must be of type atext
  1046. // A zero-length element implies a period at the beginning or end of the
  1047. // local part, or two periods together. Either way it's not allowed.
  1048. if ($elementLength === 0) if ($diagnose) return self::ISEMAIL_DOMAINEMPTYELEMENT; else return false; // Dots in wrong place
  1049. // revision 1.15: Speed up the test and get rid of "unitialized string offset" notices from PHP
  1050. // Then we need to remove all valid comments (i.e. those at the start or end of the element
  1051. if ($element[0] === '(') {
  1052. $indexBrace = strpos($element, ')');
  1053. if ($indexBrace !== false) {
  1054. if (preg_match('/(?<!\\\\)[\\(\\)]/', substr($element, 1, $indexBrace - 1)) > 0) {
  1055. if ($diagnose) return self::ISEMAIL_BADCOMMENT_START; else return false; // Illegal characters in comment
  1056. }
  1057. $element = substr($element, $indexBrace + 1, $elementLength - $indexBrace - 1);
  1058. $elementLength = strlen($element);
  1059. }
  1060. }
  1061. if ($element[$elementLength - 1] === ')') {
  1062. $indexBrace = strrpos($element, '(');
  1063. if ($indexBrace !== false) {
  1064. if (preg_match('/(?<!\\\\)(?:[\\(\\)])/', substr($element, $indexBrace + 1, $elementLength - $indexBrace - 2)) > 0)
  1065. if ($diagnose) return self::ISEMAIL_BADCOMMENT_END; else return false; // Illegal characters in comment
  1066. $element = substr($element, 0, $indexBrace);
  1067. $elementLength = strlen($element);
  1068. }
  1069. }
  1070. // Remove any leading or trailing FWS around the element (inside any comments)
  1071. $element = preg_replace("/^$FWS|$FWS\$/", '', $element);
  1072. // What's left counts towards the maximum length for this part
  1073. if ($partLength > 0) $partLength++; // for the dot
  1074. $partLength += strlen($element);
  1075. // The DNS defines domain name syntax very generally -- a
  1076. // string of labels each containing up to 63 8-bit octets,
  1077. // separated by dots, and with a maximum total of 255
  1078. // octets.
  1079. // (http://tools.ietf.org/html/rfc1123#section-6.1.3.5)
  1080. if ($elementLength > 63) if ($diagnose) return self::ISEMAIL_DOMAINELEMENTTOOLONG; else return false; // Label must be 63 characters or less
  1081. // Any ASCII graphic (printing) character other than the
  1082. // at-sign ("@"), backslash, double quote, comma, or square brackets may
  1083. // appear without quoting. If any of that list of excluded characters
  1084. // are to appear, they must be quoted
  1085. // (http://tools.ietf.org/html/rfc3696#section-3)
  1086. //
  1087. // If the hyphen is used, it is not permitted to appear at
  1088. // either the beginning or end of a label.
  1089. // (http://tools.ietf.org/html/rfc3696#section-2)
  1090. //
  1091. // Any excluded characters? i.e. 0x00-0x20, (, ), <, >, [, ], :, ;, @, \, comma, period, "
  1092. if (preg_match('/[\\x00-\\x20\\(\\)<>\\[\\]:;@\\\\,\\."]|^-|-$/', $element) > 0) {
  1093. if ($diagnose) return self::ISEMAIL_DOMAINBADCHAR; else return false;
  1094. }
  1095. }
  1096. if ($partLength > 255) if ($diagnose) return self::ISEMAIL_DOMAINTOOLONG; else return false; // Domain part must be 255 characters or less (http://tools.ietf.org/html/rfc1123#section-6.1.3.5)
  1097. if (preg_match('/^[0-9]+$/', $element) > 0) if ($diagnose) return self::ISEMAIL_TLDNUMERIC; else return false; // TLD can't be all-numeric (http://www.apps.ietf.org/rfc/rfc3696.html#sec-2)
  1098. // Check DNS?
  1099. if ($checkDNS && function_exists('checkdnsrr')) {
  1100. if (!(checkdnsrr($domain, 'A') || checkdnsrr($domain, 'MX'))) {
  1101. if ($diagnose) return self::ISEMAIL_DOMAINNOTFOUND; else return false; // Domain doesn't actually exist
  1102. }
  1103. }
  1104. }
  1105. // Eliminate all other factors, and the one which remains must be the truth.
  1106. // (Sherlock Holmes, The Sign of Four)
  1107. if ($diagnose) return self::ISEMAIL_VALID; else return true;
  1108. }
  1109. }
  1110. // End of class ezUser_common
  1111. /**
  1112. * Password reset handling for ezUser
  1113. *
  1114. * @package ezUser
  1115. */
  1116. interface I_ezUser_reset {
  1117. // Methods may be commented out to reduce the attack surface when they are
  1118. // not required. Uncomment them if you need them.
  1119. // public /*.string.*/ function id();
  1120. public /*.string.*/ function resetKey();
  1121. // public /*.DateTime.*/ function expires();
  1122. public /*.string.*/ function data();
  1123. public /*.void.*/ function initialize();
  1124. public /*.void.*/ function setId(/*.string.*/ $id);
  1125. public /*.void.*/ function setData(/*.string.*/ $data);
  1126. }
  1127. /**
  1128. * Password reset handling for ezUser
  1129. *
  1130. * @package ezUser
  1131. */
  1132. class ezUser_reset extends ezUser_common implements I_ezUser_reset {
  1133. private /*.string.*/ $id;
  1134. private /*.string.*/ $resetKey;
  1135. private /*.DateTime.*/ $expires;
  1136. // Methods may be commented out to reduce the attack surface when they are
  1137. // not required. Uncomment them if you need them.
  1138. // public /*.string.*/ function id() {return $this->id;}
  1139. public /*.string.*/ function resetKey() {return $this->resetKey;}
  1140. // public /*.DateTime.*/ function expires() {return $this->expires;}
  1141. public /*.string.*/ function data() {return serialize(array($this->id, $this->resetKey, serialize($this->expires)));}
  1142. public /*.void.*/ function initialize() {
  1143. $date = new DateTime();
  1144. $date->modify('+1 day');
  1145. $this->resetKey = self::makeUniqueKey($this->id);
  1146. $this->expires = $date;
  1147. }
  1148. public /*.void.*/ function setId(/*.string.*/ $id) {
  1149. $this->id = $id;
  1150. $this->initialize();
  1151. }
  1152. public /*.void.*/ function setData(/*.string.*/ $data) {
  1153. list($this->id, $this->resetKey, $expiresString) = /*.(array[int]string).*/ unserialize($data);
  1154. $this->expires = /*.(DateTime).*/ unserialize($expiresString);
  1155. }
  1156. }
  1157. // End of class ezUser_reset
  1158. /**
  1159. * This class encapsulates all the functions needed for an app to interact
  1160. * with a user. It has no knowledge of how user information is persisted.
  1161. *
  1162. * @package ezUser
  1163. */
  1164. interface I_ezUser_base extends I_ezUser_common {
  1165. // REST interface actions
  1166. const ACTION = 'action',
  1167. ACTION_ABOUT = 'about',
  1168. ACTION_ABOUTTEXT = 'abouttext',
  1169. ACTION_ACCOUNT = 'account',
  1170. ACTION_ACCOUNTFORM = 'accountform',
  1171. ACTION_ACCOUNTWIZARD = 'accountwizard',
  1172. ACTION_BITMAP = 'bitmap',
  1173. ACTION_BODY = 'body',
  1174. ACTION_CANCEL = 'cancel',
  1175. ACTION_CONTAINER = 'container',
  1176. ACTION_DASHBOARD = 'dashboard',
  1177. ACTION_JAVASCRIPT = 'js',
  1178. ACTION_MAIN = 'controlpanel',
  1179. ACTION_RESEND = 'resend', // Resend verification email
  1180. ACTION_RESET = 'reset', // Process password reset link
  1181. ACTION_RESETPASSWORD = 'resetpassword', // Initiate password reset processing
  1182. ACTION_RESETREQUEST = 'resetrequest', // Request password reset form
  1183. ACTION_RESULTFORM = 'resultform',
  1184. ACTION_RESULTTEXT = 'resulttext',
  1185. ACTION_SIGNIN = 'signin',
  1186. ACTION_SIGNOUT = 'signout',
  1187. ACTION_SOURCECODE = 'source',
  1188. ACTION_STATUSTEXT = 'statustext',
  1189. ACTION_STYLESHEET = 'css',
  1190. ACTION_VALIDATE = 'validate', // Validate registration form details
  1191. ACTION_VERIFY = 'verify', // Verify verification email
  1192. // Keys for the user data array members
  1193. TAGNAME_CONFIRM = 'confirm',
  1194. TAGNAME_DATA = 'data',
  1195. TAGNAME_EMAIL = 'email',
  1196. TAGNAME_FIRSTNAME = 'firstname',
  1197. TAGNAME_FULLNAME = 'fullname',
  1198. TAGNAME_ID = 'id',
  1199. TAGNAME_LASTNAME = 'lastname',
  1200. TAGNAME_NEWUSER = 'newuser',
  1201. TAGNAME_PASSWORD = 'password',
  1202. TAGNAME_REMEMBERME = 'rememberme',
  1203. TAGNAME_RESETKEY = 'resetkey',
  1204. TAGNAME_RESETDATA = 'resetdata',
  1205. TAGNAME_SAVEDPASSWORD = 'usesavedpassword',
  1206. TAGNAME_STATUS = 'status',
  1207. TAGNAME_STAYSIGNEDIN = 'staysignedin',
  1208. TAGNAME_USER = 'user',
  1209. TAGNAME_USERNAME = 'username',
  1210. TAGNAME_VERIFICATIONKEY = 'verificationkey',
  1211. TAGNAME_VERBOSE = 'verbose',
  1212. TAGNAME_WIZARD = 'wizard',
  1213. // Registration status codes
  1214. STATUS_UNKNOWN = 0,
  1215. STATUS_PENDING = 1,
  1216. STATUS_CONFIRMED = 2,
  1217. STATUS_INACTIVE = 3,
  1218. // Authentication result codes
  1219. RESULT_UNDEFINED = 0,
  1220. RESULT_SUCCESS = 1,
  1221. RESULT_UNKNOWNUSER = 2,
  1222. RESULT_BADPASSWORD = 3,
  1223. RESULT_UNKNOWNACTION = 4,
  1224. RESULT_NOACTION = 5,
  1225. RESULT_FAILEDAUTOSIGNIN = 6,
  1226. // Validation result codes
  1227. RESULT_VALIDATED = 32,
  1228. RESULT_NOID = 33,
  1229. RESULT_NOUSERNAME = 34,
  1230. RESULT_NOEMAIL = 35,
  1231. RESULT_EMAILFORMATERR = 36,
  1232. RESULT_NOPASSWORD = 37,
  1233. RESULT_NULLPASSWORD = 38,
  1234. RESULT_STATUSNAN = 39,
  1235. RESULT_RESULTNAN = 40,
  1236. RESULT_CONFIGNOTARRAY = 41,
  1237. RESULT_USERNAMEEXISTS = 42,
  1238. RESULT_EMAILEXISTS = 43,
  1239. RESULT_NOTSIGNEDIN = 44,
  1240. RESULT_INCOMPLETE = 45,
  1241. // Result codes for session and environment issues
  1242. RESULT_NOSESSION = 64,
  1243. RESULT_NOSESSIONCOOKIES = 65,
  1244. RESULT_STORAGEERR = 66,
  1245. RESULT_EMAILERR = 67,
  1246. RESULT_HEADERSSENT = 68,
  1247. // Miscellaneous constants
  1248. DELIMITER_SPACE = ' ',
  1249. STRING_TRUE = 'true',
  1250. STRING_FALSE = 'false';
  1251. public /*.int.*/ function authenticate($passwordHash = '');
  1252. public /*.string.*/ function username();
  1253. public /*.string.*/ function firstName();
  1254. public /*.string.*/ function lastName();
  1255. public /*.string.*/ function fullName();
  1256. public /*.string.*/ function email();
  1257. public /*.int.*/ function status();
  1258. public /*.boolean.*/ function authenticated();
  1259. public /*.void.*/ function setFirstName(/*.string.*/ $name);
  1260. public /*.void.*/ function setLastName(/*.string.*/ $name);
  1261. }
  1262. // End of interface I_ezUser_base
  1263. /**
  1264. * This class encapsulates all the functions needed for an app to interact
  1265. * with a user. It has no knowledge of how user information is persisted.
  1266. *
  1267. * @package ezUser
  1268. */
  1269. class ezUser_base extends ezUser_common implements I_ezUser_base {
  1270. // User data
  1271. private $keys = array (
  1272. self::TAGNAME_USERNAME ,
  1273. self::TAGNAME_EMAIL ,
  1274. self::TAGNAME_ID ,
  1275. self::TAGNAME_PASSWORD ,
  1276. self::TAGNAME_STATUS ,
  1277. self::TAGNAME_FIRSTNAME ,
  1278. self::TAGNAME_LASTNAME ,
  1279. self::TAGNAME_FULLNAME ,
  1280. self::TAGNAME_VERIFICATIONKEY,
  1281. );
  1282. private $values = array (
  1283. self::TAGNAME_USERNAME => '',
  1284. self::TAGNAME_EMAIL => '',
  1285. self::TAGNAME_ID => '',
  1286. self::TAGNAME_PASSWORD => '',
  1287. self::TAGNAME_STATUS => '0',
  1288. self::TAGNAME_FIRSTNAME => '',
  1289. self::TAGNAME_LASTNAME => '',
  1290. self::TAGNAME_FULLNAME => '',
  1291. self::TAGNAME_VERIFICATIONKEY => ''
  1292. );
  1293. // State and derived data
  1294. private $authenticated = false; // User is signed in
  1295. private $usernameIsDefault = true; // username === firstName.lastName
  1296. private $isChanged = false; // Unsaved changes?
  1297. private $result = self::RESULT_UNDEFINED; // Result of any change operation
  1298. private $config = /*.(array[string]string).*/ array(); // Configuration settings
  1299. private $errors = /*.(array[string]string).*/ array(); // Validation errors
  1300. private $signOutActions = /*.(array[int]string).*/ array(); // Things to do on signing out
  1301. private $manualSignOut = false; // User chose to sign out
  1302. // ---------------------------------------------------------------------------
  1303. // Helper methods
  1304. // ---------------------------------------------------------------------------
  1305. private /*.boolean.*/ function setValue(/*.string.*/ $key, /*.string.*/ $value) {
  1306. if ($value !== $this->values[$key]) {
  1307. $this->values[$key] = $value;
  1308. $this->isChanged = true;
  1309. return true;
  1310. } else {
  1311. return false;
  1312. }
  1313. }
  1314. private /*.string.*/ function getValue(/*.string.*/ $key) {
  1315. $value = '';
  1316. if (!in_array($key, $this->keys)) return $value;
  1317. switch ($key) {
  1318. case self::TAGNAME_VERIFICATIONKEY:
  1319. if ((int) $this->getValue(self::TAGNAME_STATUS) === self::STATUS_PENDING) $value = $this->values[$key];
  1320. break;
  1321. case self::TAGNAME_ID:
  1322. if ($this->values[$key] === '') $this->setValue($key, self::makeId());
  1323. $value = $this->values[$key];
  1324. break;
  1325. default:
  1326. $value = $this->values[$key];
  1327. break;
  1328. }
  1329. return $value;
  1330. }
  1331. // ---------------------------------------------------------------------------
  1332. // Authenticate
  1333. // ---------------------------------------------------------------------------
  1334. public /*.int.*/ function authenticate($passwordHash = '') {
  1335. if (empty($passwordHash)) {
  1336. // Sign out
  1337. $this->authenticated = false;
  1338. $this->manualSignOut = true;
  1339. $result = self::RESULT_SUCCESS;
  1340. } else {
  1341. // Sign in
  1342. self::checkSession();
  1343. $sessionHash = hash(self::HASH_FUNCTION, session_id() . hash(self::HASH_FUNCTION, $_SERVER['REMOTE_ADDR'] . $this->values[self::TAGNAME_PASSWORD]));
  1344. //error_log(date('Y-m-d H:i:s', time()) . "\t" . session_id() . '|' . $_SERVER['REMOTE_ADDR'] . '|' . $this->values[self::TAGNAME_PASSWORD] . '|' . hash(self::HASH_FUNCTION, $_SERVER['REMOTE_ADDR'] . $this->values[self::TAGNAME_PASSWORD]) . "|$sessionHash|$passwordHash\n", 3, dirname(__FILE__) . self::URL_SEPARATOR . '.ezuser-log.php'); // Debug
  1345. $this->authenticated = ($passwordHash === $sessionHash);
  1346. if ($this->authenticated) {
  1347. $result = self::RESULT_SUCCESS;
  1348. $this->manualSignOut = false;
  1349. } else {
  1350. $result = self::RESULT_BADPASSWORD;
  1351. }
  1352. }
  1353. $this->result = $result;
  1354. return $result;
  1355. }
  1356. // ---------------------------------------------------------------------------
  1357. // "Get" methods
  1358. // ---------------------------------------------------------------------------
  1359. protected /*.string.*/ function data() {return serialize($this->values);}
  1360. protected /*.string.*/ function id() {return $this->getValue(self::TAGNAME_ID);}
  1361. public /*.string.*/ function username() {return $this->getValue(self::TAGNAME_USERNAME);}
  1362. protected /*.string.*/ function passwordHash() {return $this->getValue(self::TAGNAME_PASSWORD);}
  1363. public /*.string.*/ function firstName() {return $this->getValue(self::TAGNAME_FIRSTNAME);}
  1364. public /*.string.*/ function lastName() {return $this->getValue(self::TAGNAME_LASTNAME);}
  1365. public /*.string.*/ function fullName() {return $this->getValue(self::TAGNAME_FULLNAME);}
  1366. public /*.string.*/ function email() {return $this->getValue(self::TAGNAME_EMAIL);}
  1367. protected /*.string.*/ function verificationKey() {return $this->getValue(self::TAGNAME_VERIFICATIONKEY);}
  1368. public /*.int.*/ function status() {return (int) $this->getValue(self::TAGNAME_STATUS);}
  1369. public /*.boolean.*/ function authenticated() {return $this->authenticated;}
  1370. protected /*.int.*/ function result() {return $this->result;}
  1371. protected /*.array[string]string.*/ function config() {return $this->config;}
  1372. protected /*.array[string]string.*/ function errors() {return $this->errors;}
  1373. protected /*.string.*/ function signOutActions() {return implode(self::DELIMITER_SPACE, $this->signOutActions);}
  1374. protected /*.boolean.*/ function isChanged() {return $this->isChanged;}
  1375. protected /*.boolean.*/ function manualSignOut() {return $this->manualSignOut;}
  1376. protected /*.boolean.*/ function incomplete() {
  1377. return ( empty($this->values[self::TAGNAME_USERNAME]) ||
  1378. empty($this->values[self::TAGNAME_EMAIL]) ||
  1379. empty($this->values[self::TAGNAME_ID])
  1380. );
  1381. }
  1382. // ---------------------------------------------------------------------------
  1383. // Name manipulation
  1384. // ---------------------------------------------------------------------------
  1385. private /*.string.*/ function getDefaultUsername() {
  1386. $lastName = $this->values[self::TAGNAME_LASTNAME];
  1387. $firstName = $this->values[self::TAGNAME_FIRSTNAME];
  1388. $username = strtolower($firstName . $lastName);
  1389. $username = preg_replace('/[^0-9a-z_-]/', '', $username);
  1390. return $username;
  1391. }
  1392. private /*.void.*/ function setFullName() {
  1393. $firstName = $this->values[self::TAGNAME_FIRSTNAME];
  1394. $lastName = $this->values[self::TAGNAME_LASTNAME];
  1395. $separator = (empty($firstName) || empty($lastName)) ? '' : self::DELIMITER_SPACE;
  1396. $this->setValue(self::TAGNAME_FULLNAME, $firstName . $separator . $lastName);
  1397. if ($this->usernameIsDefault) {$this->setValue(self::TAGNAME_USERNAME, $this->getDefaultUsername());}
  1398. }
  1399. private /*.void.*/ function setNamePart(/*.string.*/ $key, /*.string.*/ $name) {
  1400. $name = trim($name);
  1401. if ($this->setValue($key, $name)) $this->setFullName();
  1402. }
  1403. // ---------------------------------------------------------------------------
  1404. // "Set" methods
  1405. // ---------------------------------------------------------------------------
  1406. protected /*.void.*/ function setData(/*.string.*/ $data) {
  1407. $this->values = /*.(array[string]string).*/ unserialize($data);
  1408. }
  1409. protected /*.void.*/ function clearErrors() {
  1410. $this->errors = /*.(array[string]string).*/ array();
  1411. }
  1412. protected /*.int.*/ function setStatus(/*.int.*/ $status) {
  1413. if (!is_numeric($status)) return self::RESULT_STATUSNAN;
  1414. // If we're setting this user to Pending then generate a verification key
  1415. if ($status === self::STATUS_PENDING && $this->status() !== self::STATUS_PENDING) {
  1416. // Use the ID to generate a verification key
  1417. $this->setValue(self::TAGNAME_VERIFICATIONKEY, self::makeUniqueKey($this->id()));
  1418. }
  1419. $this->setValue(self::TAGNAME_STATUS, (string) $status);
  1420. return self::RESULT_VALIDATED;
  1421. }
  1422. protected /*.int.*/ function setResult(/*.int.*/ $result) {
  1423. if (!is_numeric($result)) return self::RESULT_RESULTNAN;
  1424. $this->result = $result;
  1425. return self::RESULT_VALIDATED;
  1426. }
  1427. protected /*.int.*/ function setConfig(/*.array[string]string.*/ $config) {
  1428. if (!is_array($config)) return self::RESULT_CONFIGNOTARRAY;
  1429. $this->config = $config;
  1430. return self::RESULT_VALIDATED;
  1431. }
  1432. public /*.void.*/ function setFirstName(/*.string.*/ $name) {$this->setNamePart(self::TAGNAME_FIRSTNAME, $name);}
  1433. public /*.void.*/ function setLastName(/*.string.*/ $name) {$this->setNamePart(self::TAGNAME_LASTNAME, $name);}
  1434. protected /*.int.*/ function setUsername($name = '') {
  1435. $this->usernameIsDefault = empty($name);
  1436. if ($this->usernameIsDefault) $name = $this->getDefaultUsername();
  1437. if (empty($name)) return self::RESULT_NOUSERNAME;
  1438. $this->setValue(self::TAGNAME_USERNAME, $name);
  1439. return self::RESULT_VALIDATED;
  1440. }
  1441. protected /*.int.*/ function setEmail(/*.string.*/ $email) {
  1442. if (empty($email)) return self::RESULT_NOEMAIL;
  1443. if (!(boolean) self::is_email($email)) {
  1444. $this->errors[self::TAGNAME_EMAIL] = $email;
  1445. return self::RESULT_EMAILFORMATERR;
  1446. }
  1447. $this->setValue(self::TAGNAME_EMAIL, $email);
  1448. return self::RESULT_VALIDATED;
  1449. }
  1450. protected /*.int.*/ function setPasswordHash(/*.string.*/ $passwordHash) {
  1451. if (empty($passwordHash)) return self::RESULT_NOPASSWORD;
  1452. if ($passwordHash === hash(self::HASH_FUNCTION, '')) return self::RESULT_NULLPASSWORD;
  1453. $this->setValue(self::TAGNAME_PASSWORD, $passwordHash);
  1454. return self::RESULT_VALIDATED;
  1455. }
  1456. protected /*.void.*/ function addSignOutAction(/*.string.*/ $action) {
  1457. if (!in_array($action, $this->signOutActions)) $this->signOutActions[] = $action;
  1458. }
  1459. protected /*.void.*/ function clearSignOutActions() {
  1460. $this->signOutActions = /*.(array[int]string).*/ array();
  1461. }
  1462. protected /*.void.*/ function clearChanges() {
  1463. $this->isChanged = false;
  1464. }
  1465. // ---------------------------------------------------------------------------
  1466. // Password reset handling
  1467. // ---------------------------------------------------------------------------
  1468. private $passwordResetFlag = false;
  1469. private /*.ezUser_reset.*/ $passwordReset;
  1470. public /*.boolean.*/ function hasPasswordReset() {return $this->passwordResetFlag;}
  1471. public /*.ezUser_reset.*/ function passwordReset($terminate = false) {
  1472. $passwordReset = new ezUser_reset();
  1473. if ($terminate) {
  1474. unset($this->passwordReset);
  1475. $this->passwordResetFlag = false;
  1476. return $passwordReset; // empty
  1477. } else {
  1478. $passwordReset->setId($this->id());
  1479. $this->passwordReset = $passwordReset;
  1480. $this->passwordResetFlag = true;
  1481. return $this->passwordReset;
  1482. }
  1483. }
  1484. }
  1485. // End of class ezUser_base
  1486. /**
  1487. * This class encapsulates all the functions needed to manage the collection
  1488. * of stored users. It interacts with the storage mechanism (e.g. database or
  1489. * XML file).
  1490. *
  1491. * @package ezUser
  1492. */
  1493. interface I_ezUser_environment extends I_ezUser_base {
  1494. // Cookie names
  1495. const COOKIE_USERNAME = 'ezuser-1',
  1496. COOKIE_PASSWORD = 'ezuser-2',
  1497. COOKIE_AUTOSIGN = 'ezuser-3',
  1498. // Storage locations
  1499. STORAGE = '.ezuser-data.php',
  1500. SETTINGS = '.ezuser-settings.php',
  1501. LOG = '.ezuser-log.php',
  1502. // Keys for the configuration settings
  1503. SETTINGS_ADMINEMAIL = 'adminEmail',
  1504. SETTINGS_PERSISTED = 'persisted',
  1505. SETTINGS_EMPTY = 'empty',
  1506. SETTINGS_ACCOUNTPAGE = 'accountPage',
  1507. SETTINGS_SECUREFOLDER = 'secureFolder',
  1508. // Miscellaneous constants
  1509. DELIMITER_EMAIL = '@';
  1510. public static /*.ezUser_base.*/ function lookup ($needle = '', $tagName = '');
  1511. public static /*.ezUser_base.*/ function save (/*.array[string]mixed.*/ $userData);
  1512. public static /*.ezUser_base.*/ function signIn ($userData = /*.(array[string]mixed).*/ array());
  1513. }
  1514. /**
  1515. * This class encapsulates all the functions needed to manage the collection
  1516. * of stored users. It interacts with the storage mechanism (e.g. database or
  1517. * XML file).
  1518. *
  1519. * @package ezUser
  1520. */
  1521. abstract class ezUser_environment extends ezUser_base implements I_ezUser_environment {
  1522. // ---------------------------------------------------------------------------
  1523. // Helper methods
  1524. // ---------------------------------------------------------------------------
  1525. protected static /*.boolean.*/ function logMessage($message = 'Unknown') {
  1526. $filename = dirname(__FILE__) . self::URL_SEPARATOR . self::LOG;
  1527. $logWhen = date('Y-m-d H:i:s', time());
  1528. return error_log("$logWhen\t$message\n", 3, $filename);
  1529. }
  1530. private static /*.DOMDocument.*/ function openStorage() {
  1531. // Connect to database or whatever our storage mechanism is in this version
  1532. // Where is the storage container?
  1533. $storage_file = realpath(dirname(__FILE__)) . self::URL_SEPARATOR . self::STORAGE;
  1534. // If storage container doesn't exist then create it
  1535. if (!is_file($storage_file)) {
  1536. $query = '?';
  1537. $html = <<<HTML
  1538. <?php header("Location: /"); $query>
  1539. <users>
  1540. </users>
  1541. HTML;
  1542. $handle = @fopen($storage_file, 'wb');
  1543. if (is_bool($handle)) exit(self::RESULT_STORAGEERR);
  1544. fwrite($handle, $html);
  1545. fclose($handle);
  1546. chmod($storage_file, 0600);
  1547. }
  1548. // Open the container for use
  1549. $storage = new DOMDocument();
  1550. $storage->load($storage_file);
  1551. return $storage;
  1552. }
  1553. // ---------------------------------------------------------------------------
  1554. private static /*.void.*/ function closeStorage(DOMDocument $storage) {
  1555. $storage_file = dirname(__FILE__) . self::URL_SEPARATOR . self::STORAGE;
  1556. for ($attempt = 0; $attempt < 3; $attempt++) {
  1557. $count = @$storage->save($storage_file);
  1558. if ((bool) $count) break;
  1559. sleep(1); // File may occasionally be locked by indexing/backups etc.
  1560. }
  1561. }
  1562. // ---------------------------------------------------------------------------
  1563. private static /*.DOMElement.*/ function findUser(DOMDocument $storage, $needle = '', $tagName = '') {
  1564. if ($needle === '') return $storage->createElement(self::TAGNAME_USER);
  1565. if ($tagName === '') $tagName = ((bool) strpos($needle,self::DELIMITER_EMAIL)) ? self::TAGNAME_EMAIL : self::TAGNAME_USERNAME;
  1566. $nodeList = $storage->getElementsByTagName($tagName);
  1567. $found = false;
  1568. for ($i = 0; $i < $nodeList->length; $i++) {
  1569. $node = $nodeList->item($i);
  1570. $found = (strcasecmp($node->nodeValue, $needle) === 0);
  1571. if ($found) break;
  1572. }
  1573. if ($found && isset($node)) {
  1574. /*.object.*/ $userElement_PHPLint = $node->parentNode; // PHPLint-compliant typecasting (yawn)
  1575. $userElement = /*.(DOMElement).*/ $userElement_PHPLint;
  1576. return $userElement;
  1577. } else {
  1578. return $storage->createElement(self::TAGNAME_USER);
  1579. }
  1580. }
  1581. // ---------------------------------------------------------------------------
  1582. public static /*.ezUser_base.*/ function lookup($needle = '', $tagName = '') {
  1583. $ezUser = new ezUser_base();
  1584. if ($needle === '') return $ezUser;
  1585. if ($tagName === '' || $tagName === self::TAGNAME_USERNAME || $tagName === self::TAGNAME_EMAIL) {
  1586. $ezUser->setUsername($needle); // Will get overwritten if we successfully find the user in the database
  1587. }
  1588. $storage = self::openStorage();
  1589. $record = self::findUser($storage, $needle, $tagName);
  1590. if ($record->hasChildNodes()) {
  1591. $data = $record->getElementsByTagName(self::TAGNAME_DATA)->item(0)->nodeValue;
  1592. if (!empty($data)) $ezUser->setData($data);
  1593. $nodeList = $record->getElementsByTagName(self::TAGNAME_RESETDATA);
  1594. if ((bool) $nodeList->length) {
  1595. $data = $nodeList->item(0)->nodeValue;
  1596. if (!empty($data)) {
  1597. $passwordReset = $ezUser->passwordReset();
  1598. $passwordReset->setData($data);
  1599. }
  1600. }
  1601. }
  1602. return $ezUser;
  1603. }
  1604. // ---------------------------------------------------------------------------
  1605. // Functions for sending stuff to the browser
  1606. // ---------------------------------------------------------------------------
  1607. protected static /*.void.*/ function sendContent(/*.string.*/ $content, $container = '', $contentType = 'text/html') {
  1608. // Send headers first
  1609. if (!headers_sent()) {
  1610. // $package = 'ezuser';
  1611. if ($container === '') $container = 'ezuser';
  1612. //header("Container-length: " . strlen($container)); // debug
  1613. header('Package: ezUser');
  1614. header("ezUser-container: $container");
  1615. header("Content-type: $contentType");
  1616. }
  1617. // Send content
  1618. echo $content;
  1619. /* Comment out profiling statements if not needed
  1620. // Send profiling data as a comment
  1621. global $ezuser_profile;
  1622. if (count($ezuser_profile) > 0) {
  1623. $ezuser_profile['response'] = ezuser_time();
  1624. if ($contentType === 'text/javascript' || $contentType === 'text/css') {
  1625. $commentStart = '/' . '*';
  1626. $commentEnd = '*' . '/';
  1627. } else {
  1628. $commentStart = '<!--';
  1629. $commentEnd = '-->';
  1630. }
  1631. echo "\n$commentStart\n";
  1632. $previous = reset($ezuser_profile);
  1633. while (list($key, $value) = each($ezuser_profile)) {
  1634. $elapsed = round($value - $previous, 4);
  1635. $previous = $value;
  1636. echo "$key\t$value\t$elapsed\n";
  1637. }
  1638. echo "$commentEnd\n";
  1639. }
  1640. */
  1641. }
  1642. protected static /*.string.*/ function resultText(/*.int.*/ $result, $more = '', $sendToBrowser = false) {
  1643. switch ($result) {
  1644. // Authentication results
  1645. case self::RESULT_UNDEFINED: $text = "Undefined"; break;
  1646. case self::RESULT_SUCCESS: $text = "Success"; break;
  1647. case self::RESULT_UNKNOWNUSER: $text = "Username not recognised"; break;
  1648. case self::RESULT_BADPASSWORD: $text = "Password is wrong"; break;
  1649. case self::RESULT_UNKNOWNACTION: $text = "Unrecognised action"; break;
  1650. case self::RESULT_NOACTION: $text = "No action specified"; break;
  1651. // Registration and validation results
  1652. case self::RESULT_VALIDATED: $text = "Validation was successful"; break;
  1653. case self::RESULT_NOID: $text = "ID cannot be blank"; break;
  1654. case self::RESULT_NOUSERNAME: $text = "The username cannot be blank"; break;
  1655. case self::RESULT_NOEMAIL: $text = "Please provide an email address"; break;
  1656. case self::RESULT_EMAILFORMATERR: $text = "Incorrect email address format"; break;
  1657. case self::RESULT_NOPASSWORD: $text = "Password hash cannot be blank"; break;
  1658. case self::RESULT_NULLPASSWORD: $text = "Password cannot be blank"; break;
  1659. case self::RESULT_STATUSNAN: $text = "Status code must be numeric"; break;
  1660. case self::RESULT_RESULTNAN: $text = "Result code must be numeric"; break;
  1661. case self::RESULT_CONFIGNOTARRAY: $text = "Configuration settings must be an array"; break;
  1662. case self::RESULT_USERNAMEEXISTS: $text = "This username already exists"; break;
  1663. case self::RESULT_EMAILEXISTS: $text = "Email address is already registered"; break;
  1664. case self::RESULT_NOTSIGNEDIN: $text = "You must be signed in to update your account"; break;
  1665. case self::RESULT_INCOMPLETE: $text = "Not enough information to update the account"; break;
  1666. // Session and environment issues
  1667. case self::RESULT_NOSESSION: $text = "No session data available"; break;
  1668. case self::RESULT_NOSESSIONCOOKIES: $text = "Session cookies are not enabled"; break;
  1669. case self::RESULT_STORAGEERR: $text = "Error with stored user details"; break;
  1670. case self::RESULT_EMAILERR: $text = "Error sending email"; break;
  1671. case self::RESULT_HEADERSSENT: $text = "Headers already sent"; break;
  1672. default: $text = "Unknown result code"; break;
  1673. }
  1674. if ($more !== '') $text .= ": $more";
  1675. if ($sendToBrowser) {self::sendContent($text); return '';} else return $text;
  1676. }
  1677. // ---------------------------------------------------------------------------
  1678. // Sign-in and session variables
  1679. // ---------------------------------------------------------------------------
  1680. protected static /*.string.*/ function getInstanceId($container = 'ezuser') {
  1681. return ($container === self::ACTION_MAIN || $container === 'ezuser') ? 'ezuser' : "ezuser-$container";
  1682. }
  1683. protected static /*.void.*/ function setSessionObject(ezUser_base $ezUser, $instance = 'ezuser') {
  1684. self::checkSession();
  1685. $instanceId = self::getInstanceId($instance);
  1686. $_SESSION[$instanceId] = $ezUser;
  1687. /*
  1688. $debug_isset = (isset($_SESSION)) ? 'true' : 'false'; // debug
  1689. $debug_isarray = ((isset($_SESSION)) && (is_array($_SESSION))) ? 'true' : 'false'; // debug
  1690. $debug_keyexists = ((isset($_SESSION)) && (is_array($_SESSION)) && (array_key_exists($instanceId, $_SESSION))) ? 'true' : 'false'; // debug
  1691. $debug_result = ((isset($_SESSION)) && (is_array($_SESSION)) && (array_key_exists($instanceId, $_SESSION))) ? $_SESSION[$instanceId]->result() : 'n/a'; // debug
  1692. self::logMessage("setSessionObject|\$_SESSION exists: $debug_isset|\$_SESSION is array: $debug_isarray|\$instanceId = $instanceId|array key exists: $debug_keyexists|result = $debug_result|session_id = " . session_id()); // debug
  1693. */
  1694. }
  1695. private static /*.boolean.*/ function autoSignInAvailable(){
  1696. return (
  1697. array_key_exists(self::COOKIE_AUTOSIGN, $_COOKIE) &&
  1698. array_key_exists(self::COOKIE_USERNAME, $_COOKIE) &&
  1699. ($_COOKIE[self::COOKIE_AUTOSIGN] === self::STRING_TRUE) &&
  1700. ($_COOKIE[self::COOKIE_USERNAME] !== '')
  1701. );
  1702. }
  1703. public static /*.ezUser_base.*/ function signIn($userData = /*.(array[string]mixed).*/ array()) {
  1704. $autoSignInRequest = (count($userData) === 0);
  1705. $logEntry = 'Sign in';
  1706. if ($autoSignInRequest) {
  1707. if (self::autoSignInAvailable()) {
  1708. $userData[self::COOKIE_USERNAME] = (string) $_COOKIE[self::COOKIE_USERNAME];
  1709. $userData[self::COOKIE_PASSWORD] = hash(self::HASH_FUNCTION, session_id() . (string) $_COOKIE[self::COOKIE_PASSWORD]);
  1710. $logEntry .= '|auto';
  1711. } else {
  1712. $userData[self::COOKIE_USERNAME] = '';
  1713. $userData[self::COOKIE_PASSWORD] = '';
  1714. $logEntry .= '|auto not available';
  1715. }
  1716. } else {
  1717. $logEntry .= '|manual';
  1718. }
  1719. $username = (string) $userData[self::COOKIE_USERNAME];
  1720. $password = (string) $userData[self::COOKIE_PASSWORD];
  1721. $logEntry .= "|$username|$password";
  1722. if ($username === '') {
  1723. $ezUser = new ezUser_base();
  1724. } else {
  1725. $ezUser = self::lookup($username);
  1726. if ($ezUser->status() === self::STATUS_UNKNOWN) {
  1727. $ezUser->setResult(($autoSignInRequest) ? self::RESULT_FAILEDAUTOSIGNIN : self::RESULT_UNKNOWNUSER);
  1728. } else {
  1729. $ezUser->authenticate($password); // Sets result itself
  1730. }
  1731. }
  1732. self::setSessionObject($ezUser);
  1733. self::logMessage($logEntry . '|' . $ezUser->result());
  1734. return $ezUser;
  1735. }
  1736. protected static /*.ezUser_base.*/ function getSessionObject($instance = 'ezuser') {
  1737. $file = '';
  1738. $lineInt = 0;
  1739. // There may already be a session in progress. We will use the existing
  1740. // session if possible.
  1741. if ((int) ini_get('session.use_cookies') === 0) {
  1742. echo self::resultText(self::RESULT_NOSESSIONCOOKIES);
  1743. die(self::RESULT_NOSESSIONCOOKIES);
  1744. } else if (headers_sent($file, $lineInt)) {
  1745. $line = (string) $lineInt;
  1746. echo self::resultText(self::RESULT_HEADERSSENT, "$file (line $line)");
  1747. die(self::RESULT_HEADERSSENT);
  1748. } else {
  1749. self::checkSession();
  1750. }
  1751. $instanceId = self::getInstanceId($instance);
  1752. /*
  1753. $debug_isset = (isset($_SESSION)) ? 'true' : 'false'; // debug
  1754. $debug_isarray = ((isset($_SESSION)) && (is_array($_SESSION))) ? 'true' : 'false'; // debug
  1755. $debug_keyexists = ((isset($_SESSION)) && (is_array($_SESSION)) && (array_key_exists($instanceId, $_SESSION))) ? 'true' : 'false'; // debug
  1756. $debug_result = ((isset($_SESSION)) && (is_array($_SESSION)) && (array_key_exists($instanceId, $_SESSION))) ? $_SESSION[$instanceId]->result() : 'n/a'; // debug
  1757. self::logMessage("getSessionObject|\$_SESSION exists: $debug_isset|\$_SESSION is array: $debug_isarray|\$instanceId = $instanceId|array key exists: $debug_keyexists|result = $debug_result|session_id = " . session_id()); // debug
  1758. */
  1759. if (!array_key_exists($instanceId, $_SESSION)) $_SESSION[$instanceId] = self::signIn(); // Returns ezUser object, signed in if possible
  1760. $ezUser = /*.(ezUser_base).*/ $_SESSION[$instanceId];
  1761. if (
  1762. !$ezUser->authenticated() &&
  1763. !$ezUser->manualSignOut() &&
  1764. self::autoSignInAvailable()
  1765. ) $_SESSION[$instanceId] = self::signIn(); // Returns ezUser object, signed in if possible
  1766. return /*.(ezUser_base).*/ $_SESSION[$instanceId];
  1767. }
  1768. // ---------------------------------------------------------------------------
  1769. // Configuration settings
  1770. // ---------------------------------------------------------------------------
  1771. protected static /*.string.*/ function thisURL() {
  1772. return self::getURL(self::URL_MODE_PATH, 'ezuser.php');
  1773. }
  1774. private static /*.array[string]string.*/ function loadConfig() {
  1775. $ezUser = self::getSessionObject();
  1776. $config = $ezUser->config();
  1777. $settingsFile = realpath(dirname(__FILE__) . self::URL_SEPARATOR . self::SETTINGS);
  1778. // If configuration settings file doesn't exist then use default settings
  1779. if (($settingsFile === false) || !is_file($settingsFile)) {
  1780. $config[self::SETTINGS_EMPTY] = self::STRING_TRUE;
  1781. } else {
  1782. // Open the vessel
  1783. $storage = new DOMDocument();
  1784. $storage->load($settingsFile);
  1785. $nodeList = $storage->getElementsByTagName('settings')->item(0)->childNodes;
  1786. for ($i = 0; $i < $nodeList->length; $i++) {
  1787. $node = $nodeList->item($i);
  1788. if ($node->nodeType == XML_ELEMENT_NODE) {
  1789. $config[$node->nodeName] = $node->nodeValue;
  1790. }
  1791. }
  1792. }
  1793. $config[self::SETTINGS_PERSISTED] = self::STRING_TRUE;
  1794. $ezUser->setConfig($config);
  1795. return $config;
  1796. }
  1797. private static /*.array[string]string.*/ function getSettings() {
  1798. $ezUser = self::getSessionObject();
  1799. $config = $ezUser->config();
  1800. if (!is_array($config)) {$config = self::loadConfig();}
  1801. if (!array_key_exists(self::SETTINGS_PERSISTED, $config)) {$config = self::loadConfig();}
  1802. if ($config[self::SETTINGS_PERSISTED] !== self::STRING_TRUE) {$config = self::loadConfig();}
  1803. return $config;
  1804. }
  1805. protected static /*.string.*/ function getSetting(/*.string.*/ $setting) {
  1806. $config = self::getSettings();
  1807. $thisSetting = (array_key_exists($setting, $config)) ? $config[$setting] : '';
  1808. return $thisSetting;
  1809. }
  1810. // ---------------------------------------------------------------------------
  1811. private static /*.boolean.*/ function sendEmail($to = '', $subject = '', $message = '', $additional_headers = '') {
  1812. if ($to === '') return false; // Can't send to an empty address
  1813. if ($subject.$message === '') return false; // Can't send empty subject and message - that's just creepy
  1814. $from = self::getSetting(self::SETTINGS_ADMINEMAIL);
  1815. $from = ($from === '') ? 'webmaster' : $from;
  1816. // If there's no domain, then assume same as this host
  1817. if (strpos($from, self::DELIMITER_EMAIL) === false) {
  1818. $host = self::getURL(self::URL_MODE_HOST);
  1819. $domain = (substr_count($host, '.') > 1) ? substr($host, strpos($host, '.') + 1) : $host;
  1820. $from .= self::DELIMITER_EMAIL . $domain;
  1821. }
  1822. // Extra headers
  1823. $additional_headers .= "From: $from\r\n";
  1824. date_default_timezone_set(@date_default_timezone_get()); // E_STRICT needs this or it complains about the mail function
  1825. // Try three times to send the mail
  1826. $success = false;
  1827. for ($i = 0; $i < 3; $i++) {
  1828. if ($i > 0) self::logMessage('Failed to send email, retrying');
  1829. $success = @mail($to, $subject, $message, $additional_headers);
  1830. if ($success) break;
  1831. }
  1832. if (!$success) self::logMessage('Failed to send email');
  1833. return $success;
  1834. }
  1835. // ---------------------------------------------------------------------------
  1836. private static /*.int.*/ function is_duplicate(/*.string.*/ $username_or_email, /*.string.*/ $id) {
  1837. $resultCode = ((bool) strpos($username_or_email, self::DELIMITER_EMAIL)) ? self::RESULT_EMAILEXISTS : self::RESULT_USERNAMEEXISTS;
  1838. $ezUser = self::lookup($username_or_email);
  1839. return ($ezUser->status() === self::STATUS_UNKNOWN) || ($ezUser->id() === $id) ? self::RESULT_VALIDATED : $resultCode;
  1840. }
  1841. // ---------------------------------------------------------------------------
  1842. // Storage methods
  1843. // ---------------------------------------------------------------------------
  1844. private static /*.void.*/ function addElement (DOMDocument $storage, DOMElement $record, /*.string.*/ $tagName, /*.string.*/ $value) {
  1845. $record->appendChild($storage->createTextNode("\n\t\t")); // XML formatting
  1846. $record->appendChild($storage->createElement($tagName, $value));
  1847. }
  1848. private static /*.DOMElement.*/ function createRecord(DOMDocument $storage, ezUser_base $ezUser) {
  1849. $record = $storage->createElement(self::TAGNAME_USER);
  1850. self::addElement($storage, $record, self::TAGNAME_USERNAME, $ezUser->username()); // Add username
  1851. self::addElement($storage, $record, self::TAGNAME_EMAIL, $ezUser->email()); // Add email address
  1852. self::addElement($storage, $record, self::TAGNAME_ID, $ezUser->id()); // Add id
  1853. self::addElement($storage, $record, self::TAGNAME_DATA, $ezUser->data()); // Add data blob
  1854. // Add verification key if necessary
  1855. $verificationKey = $ezUser->verificationKey();
  1856. if (!empty($verificationKey)) {
  1857. self::addElement($storage, $record, self::TAGNAME_VERIFICATIONKEY, $verificationKey); // Add verification key
  1858. }
  1859. // Add password reset data if necessary
  1860. if ($ezUser->hasPasswordReset()) {
  1861. $passwordReset = $ezUser->passwordReset();
  1862. self::addElement($storage, $record, self::TAGNAME_RESETKEY, $passwordReset->resetKey()); // Add password reset key
  1863. self::addElement($storage, $record, self::TAGNAME_RESETDATA, $passwordReset->data()); // Add password reset data
  1864. }
  1865. self::addElement($storage, $record, 'updated', gmdate("Y-m-d H:i:s (T)")); // Note when the record was updated
  1866. $record->appendChild($storage->createTextNode("\n\t")); // XML formatting
  1867. return $record;
  1868. }
  1869. // ---------------------------------------------------------------------------
  1870. private static /*.int.*/ function add(ezUser_base $ezUser) {
  1871. $storage = self::openStorage();
  1872. $record = self::createRecord($storage, $ezUser);
  1873. $users = $storage->getElementsByTagName('users')->item(0);
  1874. $users->appendChild($storage->createTextNode("\t")); // XML formatting
  1875. $users->appendChild($record);
  1876. $users->appendChild($storage->createTextNode("\n")); // XML formatting
  1877. self::closeStorage($storage);
  1878. return self::RESULT_SUCCESS;
  1879. }
  1880. // ---------------------------------------------------------------------------
  1881. private static /*.int.*/ function update(ezUser_base $ezUser) {
  1882. $storage = self::openStorage();
  1883. $oldRecord = self::findUser($storage, $ezUser->id(), self::TAGNAME_ID);
  1884. if (!$oldRecord->hasChildNodes()) return self::RESULT_STORAGEERR;
  1885. $newRecord = self::createRecord($storage, $ezUser);
  1886. $oldRecord->parentNode->replaceChild($newRecord, $oldRecord);
  1887. self::closeStorage($storage);
  1888. return self::RESULT_SUCCESS;
  1889. }
  1890. // ---------------------------------------------------------------------------
  1891. // Account verification
  1892. // ---------------------------------------------------------------------------
  1893. protected static /*.boolean.*/ function verify_notify($username_or_email = '') {
  1894. $ezUser = self::lookup($username_or_email);
  1895. if ($ezUser->status() !== self::STATUS_PENDING) return false; // Only send confirmation email to users who are pending verification
  1896. // Message - SMTP needs CRLF not a bare LF (http://cr.yp.to/docs/smtplf.html)
  1897. $URL = self::getURL(self::URL_MODE_ALL, 'ezuser.php');
  1898. $host = self::getURL(self::URL_MODE_HOST);
  1899. $message = "Somebody calling themselves " . $ezUser->fullName() . " created an account at $host using this email address.\r\n";
  1900. $message .= "If it was you please click on the following link to verify the account.\r\n\r\n";
  1901. $message .= "$URL?" . self::ACTION_VERIFY . "=" . $ezUser->verificationKey() . "\r\n\r\n";
  1902. $message .= "After you click the link your account will be fully functional.\r\n";
  1903. // Send it
  1904. return self::sendEmail($ezUser->email(), 'New account confirmation', $message);
  1905. }
  1906. protected static /*.void.*/ function verify_update(ezUser_base $ezUser, /*.string.*/ $verificationKey) {
  1907. if ($ezUser->status() === self::STATUS_PENDING && $ezUser->verificationKey() === $verificationKey) {
  1908. $ezUser->setStatus(self::STATUS_CONFIRMED);
  1909. self::update($ezUser);
  1910. }
  1911. }
  1912. // ---------------------------------------------------------------------------
  1913. public static /*.ezUser_base.*/ function save(/*.array[string]mixed.*/ $userData) {
  1914. $result = self::RESULT_VALIDATED;
  1915. $newUser = (array_key_exists(self::TAGNAME_NEWUSER, $userData) && ($userData[self::TAGNAME_NEWUSER] === self::STRING_TRUE)) ? true : false;
  1916. $emailChanged = false;
  1917. $usernameChanged = false;
  1918. $ezUser = self::getSessionObject(self::ACTION_ACCOUNT);
  1919. if (!$newUser && $ezUser->authenticated()) $ezUser->clearErrors(); else $newUser = true;
  1920. if ($newUser) {
  1921. $ezUser = new ezUser_base();
  1922. self::setSessionObject($ezUser, self::ACTION_ACCOUNT);
  1923. }
  1924. // Update email address
  1925. if (array_key_exists(self::TAGNAME_EMAIL, $userData)) {
  1926. $email = (string) $userData[self::TAGNAME_EMAIL];
  1927. $emailChanged = ($email !== $ezUser->email());
  1928. $thisResult = $ezUser->setEmail($email);
  1929. $result = ($result === self::RESULT_VALIDATED) ? $thisResult : $result;
  1930. } else $email = '';
  1931. // Update username
  1932. if (array_key_exists(self::COOKIE_USERNAME, $userData)) {
  1933. $username = (string) $userData[self::COOKIE_USERNAME];
  1934. $usernameChanged = ($username !== $ezUser->username());
  1935. $thisResult = $ezUser->setUsername($username);
  1936. $result = ($result === self::RESULT_VALIDATED) ? $thisResult : $result;
  1937. } else $username = '';
  1938. // Update password
  1939. if (array_key_exists(self::COOKIE_PASSWORD, $userData)) {
  1940. $passwordHash = (string) $userData[self::COOKIE_PASSWORD];
  1941. $thisResult = $ezUser->setPasswordHash($passwordHash);
  1942. $result = ($result === self::RESULT_VALIDATED) ? $thisResult : $result;
  1943. }
  1944. // Update first name and last name
  1945. if (array_key_exists(self::TAGNAME_FIRSTNAME, $userData)) $ezUser->setFirstName((string) $userData[self::TAGNAME_FIRSTNAME]);
  1946. if (array_key_exists(self::TAGNAME_LASTNAME, $userData)) $ezUser->setLastName ((string) $userData[self::TAGNAME_LASTNAME]);
  1947. // Check for duplicates
  1948. $id = $ezUser->id();
  1949. if (($result === self::RESULT_VALIDATED) && $emailChanged) $result = self::is_duplicate($email, $id);
  1950. if (($result === self::RESULT_VALIDATED) && $usernameChanged) $result = self::is_duplicate($username, $id);
  1951. // Final checks and update
  1952. if ($result === self::RESULT_VALIDATED) {
  1953. if ($ezUser->isChanged()) {
  1954. if ($newUser || $emailChanged) $ezUser->setStatus(self::STATUS_PENDING);
  1955. if ($ezUser->incomplete()) {
  1956. $result = self::RESULT_INCOMPLETE;
  1957. } else {
  1958. $result = ($newUser) ? self::add($ezUser) : self::update($ezUser);
  1959. if ($result === self::RESULT_SUCCESS) $ezUser->clearChanges();
  1960. if ($newUser || $emailChanged) self::verify_notify($email);
  1961. }
  1962. } else {
  1963. $result = self::RESULT_SUCCESS;
  1964. }
  1965. }
  1966. $ezUser->setResult($result);
  1967. return $ezUser;
  1968. }
  1969. // ---------------------------------------------------------------------------
  1970. // Secure content handling
  1971. // ---------------------------------------------------------------------------
  1972. private static /*.array[int]string.*/ function findBestMatch(/*.array[int]string.*/ $refererElements, /*.string.*/ $folder) {
  1973. $refererCount = count($refererElements);
  1974. $name = $refererElements[$refererCount - 1];
  1975. $filename = realpath("$folder/$name");
  1976. $score = 0;
  1977. // Is there a match in this folder?
  1978. if (is_file($filename)) {
  1979. // compute its score by counting matching elements back from the last one
  1980. $file = (DIRECTORY_SEPARATOR !== self::URL_SEPARATOR) ? (string) str_replace(DIRECTORY_SEPARATOR, self::URL_SEPARATOR , $filename) : $filename;
  1981. $fileElements = explode(self::URL_SEPARATOR, $file);
  1982. $fileElement = end($fileElements);
  1983. $refererElement = end($refererElements);
  1984. while ((bool) $fileElement && (bool) $refererElement && ($fileElement === $refererElement)) {
  1985. $score++;
  1986. $fileElement = prev($fileElements);
  1987. $refererElement = prev($refererElements);
  1988. }
  1989. // If it's a perfect match then it's the same page as the referer
  1990. // Don't use this one or we'll get in a loop
  1991. if ($score === $refererCount) $score = 0;
  1992. }
  1993. // Check subfolders
  1994. $folders = glob("$folder/*", GLOB_ONLYDIR);
  1995. foreach ($folders as $subfolder) {
  1996. // Exclude known red herrings
  1997. $basename = basename($subfolder);
  1998. switch ($basename) {
  1999. case '_vti_cnf': // Fall-through ->
  2000. case '.git': // Fall-through ->
  2001. case '.hg': $redHerring = true; break;
  2002. default: $redHerring = false; break;
  2003. }
  2004. if (!$redHerring) {
  2005. $match = self::findBestMatch($refererElements, $subfolder);
  2006. if ((int) $match[1] > $score) {
  2007. $filename = $match[0];
  2008. $score = (int) $match[1];
  2009. break;
  2010. }
  2011. }
  2012. }
  2013. return array($filename, (string) $score);
  2014. }
  2015. // ---------------------------------------------------------------------------
  2016. protected static /*.string.*/ function getSecureContent(/*.string.*/ $referer) {
  2017. $refererElements = /*.(array[int]string).*/ array_slice(explode(self::URL_SEPARATOR, $referer), 3);
  2018. $folder = self::getSetting(self::SETTINGS_SECUREFOLDER);
  2019. if ($folder === '') $folder = dirname(realpath(__FILE__));
  2020. $match = self::findBestMatch($refererElements, $folder);
  2021. $filename = $match[0];
  2022. $html = (is_file($filename)) ? self::getFileContents($filename) : '';
  2023. $start = strpos($html, '<body>') + 6;
  2024. $length = strpos($html, '</body>') - $start;
  2025. $html = substr($html, $start, $length);
  2026. return $html;
  2027. }
  2028. // ---------------------------------------------------------------------------
  2029. // Password reset handling
  2030. // ---------------------------------------------------------------------------
  2031. private static /*.boolean.*/ function passwordReset_notify(ezUser_base $ezUser) {
  2032. $passwordReset = $ezUser->passwordReset();
  2033. // Message
  2034. $URL = self::getURL(self::URL_MODE_ALL, 'ezuser.php');
  2035. $host = self::getURL(self::URL_MODE_HOST);
  2036. $message = "A password reset was requested for an account at $host using this email address.\r\n";
  2037. $message .= "If you want to reset your password please click on the following link.\r\n\r\n";
  2038. $message .= "$URL?" . self::ACTION_RESET . "=" . $passwordReset->resetKey() . "\r\n\r\n";
  2039. $message .= "If nothing happens when you click on the link then please copy it into your browser's address bar.\r\n";
  2040. // Send it
  2041. return self::sendEmail($ezUser->email(), 'Account maintenance', $message);
  2042. }
  2043. protected static /*.boolean.*/ function passwordReset_initialize(/*.string.*/ $username_or_email) {
  2044. $ezUser = self::lookup($username_or_email);
  2045. if ($ezUser->status() === self::STATUS_UNKNOWN) return false;
  2046. $passwordReset = $ezUser->passwordReset();
  2047. $passwordReset->initialize();
  2048. return ((bool) self::update($ezUser)) ? self::passwordReset_notify($ezUser) : false;
  2049. }
  2050. protected static /*.void.*/ function passwordReset_update(ezUser_base $ezUser, /*.string.*/$passwordHash) {
  2051. if ($ezUser->hasPasswordReset()) {
  2052. $ezUser->setPasswordHash($passwordHash);
  2053. $ezUser->passwordReset(true); // Clear password reset data
  2054. self::update($ezUser);
  2055. }
  2056. }
  2057. }
  2058. // End of class ezUser_environment
  2059. /**
  2060. * This class manages the HTML, CSS and Javascript that you can include in
  2061. * your web pages to support user registration and authentication.
  2062. *
  2063. * @package ezUser
  2064. */
  2065. interface I_ezUser extends I_ezUser_environment {
  2066. // Modes for account form
  2067. const ACCOUNT_MODE_NEW = 'new',
  2068. ACCOUNT_MODE_EDIT = 'edit',
  2069. ACCOUNT_MODE_DISPLAY = 'display',
  2070. ACCOUNT_MODE_RESULT = 'result',
  2071. ACCOUNT_MODE_CANCEL = 'cancel',
  2072. // Button types
  2073. BUTTON_TYPE_ACTION = 'action',
  2074. BUTTON_TYPE_PREFERENCE = 'preference',
  2075. BUTTON_TYPE_HIDDEN = 'hidden',
  2076. // Message types
  2077. MESSAGE_TYPE_DEFAULT = 'message',
  2078. MESSAGE_TYPE_TEXT = 'text',
  2079. // Message styles
  2080. MESSAGE_STYLE_DEFAULT = 'info',
  2081. MESSAGE_STYLE_FAIL = 'fail',
  2082. MESSAGE_STYLE_TEXT = 'text',
  2083. MESSAGE_STYLE_PLAIN = 'plain',
  2084. // Miscellaneous constants
  2085. DELIMITER_PLUS = '+',
  2086. PASSWORD_MASK = '************',
  2087. STRING_LEFT = 'left',
  2088. STRING_RIGHT = 'right';
  2089. // Methods may be commented out to reduce the attack surface when they are
  2090. // not required. Uncomment them if you need them.
  2091. // public static /*.void.*/ function getStatusText (/*.int.*/ $status, $more = '');
  2092. // public static /*.void.*/ function getResultText (/*.int.*/ $result, $more = '');
  2093. // public static /*.void.*/ function getStatusDescription (/*.int.*/ $status, $more = '');
  2094. // public static /*.void.*/ function getResultDescription (/*.int.*/ $result, $more = '');
  2095. public static /*.void.*/ function getResultForm (/*.int.*/ $result, $more = '');
  2096. // public static /*.void.*/ function fatalError (/*.int.*/ $result, $more = '');
  2097. public static /*.void.*/ function getAccountForm ($mode = '', $newUser = false);
  2098. // public static /*.void.*/ function getDashboard ();
  2099. // public static /*.void.*/ function getSignInForm ();
  2100. public static /*.void.*/ function getControlPanel ($username = '');
  2101. // public static /*.void.*/ function getStyleSheet ();
  2102. // public static /*.void.*/ function getJavascript ($containerList = '');
  2103. public static /*.void.*/ function getContainer ($action = self::ACTION_MAIN);
  2104. public static /*.void.*/ function getAbout ();
  2105. public static /*.void.*/ function getAboutText ();
  2106. // public static /*.void.*/ function getSourceCode ();
  2107. }
  2108. /**
  2109. * This class manages the HTML, CSS and Javascript that you can include in
  2110. * your web pages to support user registration and authentication.
  2111. *
  2112. * @package ezUser
  2113. */
  2114. class ezUser extends ezUser_environment implements I_ezUser {
  2115. private static /*.string.*/ function getXML($html = '', $container = '') {
  2116. if (is_numeric($container) || $container === '') $container = 'ezuser'; // If passed to sendXML as an array
  2117. return "<ezuser container=\"$container\"><![CDATA[$html]]></ezuser>";
  2118. }
  2119. private static /*.void.*/ function sendXML(/*.mixed.*/ $content = '', $container = '') {
  2120. if (is_array($content)) {
  2121. // Expected array format is $content['container'] = '<html>'
  2122. $contentArray = /*.(array[]string).*/ $content;
  2123. $xmlArray = /*.(array[]string).*/ array_map('self::getXML', $contentArray, array_keys($contentArray)); // wrap each element
  2124. $xml = implode('', $xmlArray);
  2125. $xml = "<ezuser>$xml</ezuser>";
  2126. } else {
  2127. $xml = self::getXML((string) $content, $container);
  2128. }
  2129. self::sendContent($xml, $container, 'text/xml');
  2130. }
  2131. // ---------------------------------------------------------------------------
  2132. // Functions that build common HTML fragments
  2133. // ---------------------------------------------------------------------------
  2134. private static /*.string.*/ function htmlPage($body = '', $title = '', $sendToBrowser = false) {
  2135. $URL = self::thisURL();
  2136. $actionJs = self::ACTION_JAVASCRIPT;
  2137. $actionCSS = self::ACTION_STYLESHEET;
  2138. $html = <<<HTML
  2139. <!DOCTYPE html>
  2140. <html>
  2141. <head>
  2142. <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
  2143. <title>$title</title>
  2144. <script src="$URL?$actionJs"></script>
  2145. <link type="text/css" rel="stylesheet" href="$URL?$actionCSS" title="ezUser">
  2146. </head>
  2147. <body class="ezuser">
  2148. $body
  2149. </body>
  2150. </html>
  2151. HTML;
  2152. if ($sendToBrowser) {self::sendContent($html); return '';} else return $html;
  2153. }
  2154. private static /*.string.*/ function htmlContainer($action = self::ACTION_MAIN, $sendToBrowser = false) {
  2155. $baseAction = explode('=', $action);
  2156. $container = self::getInstanceId($baseAction[0]);
  2157. $actionCommand = self::ACTION;
  2158. $actionJs = self::ACTION_JAVASCRIPT;
  2159. $URL = self::thisURL();
  2160. $html = <<<HTML
  2161. <div id="$container"></div>
  2162. <script type="text/javascript">document.write(unescape('%3Cscript src="$URL?$actionCommand=$actionJs"%3E%3C/script%3E'));</script>
  2163. <script type="text/javascript">ezUser.ajax.execute('$action');</script>
  2164. HTML;
  2165. if ($sendToBrowser) {self::sendContent($html); return '';} else return $html;
  2166. }
  2167. private static /*.string.*/ function htmlInputText($styleFloat = self::STRING_RIGHT) {
  2168. $onKeyUp = 'ezUser.keyUp';
  2169. return <<<HTML
  2170. class = "ezuser-text ezuser-$styleFloat"
  2171. onkeyup = "$onKeyUp(event)"
  2172. size = "40"
  2173. HTML;
  2174. }
  2175. private static /*.string.*/ function htmlButton(/*.string.*/ $type, $styleFloat = self::STRING_RIGHT, $verbose = false) {
  2176. $classVerbose = ($verbose) ? ' ezuser-' . self::BUTTON_TYPE_PREFERENCE . '-' . self::TAGNAME_VERBOSE : '';
  2177. $styleString = ($type === self::BUTTON_TYPE_HIDDEN) ? 'ezuser-' . self::BUTTON_TYPE_ACTION . " ezuser-$type" : "ezuser-$type";
  2178. $setButtonState = 'ezUser.setButtonState';
  2179. $onClick = 'ezUser.click';
  2180. return <<<HTML
  2181. type = "button"
  2182. class = "ezuser-button ezuser-$styleFloat $styleString$classVerbose ezuser-buttonstate-0"
  2183. onclick = "$onClick(this)"
  2184. onmouseover = "$setButtonState(this, 1, true)"
  2185. onmouseout = "$setButtonState(this, 1, false)"
  2186. onfocus = "$setButtonState(this, 2, true)"
  2187. onblur = "$setButtonState(this, 2, false)"
  2188. HTML;
  2189. }
  2190. private static /*.string.*/ function htmlMessage($message = '', $style = self::MESSAGE_STYLE_DEFAULT, $container = '', $type = self::MESSAGE_TYPE_DEFAULT, $styleFloat = self::STRING_RIGHT) {
  2191. $style = ($message === '') ? 'hidden' : $style;
  2192. $message = "<p class=\"ezuser-message-$style\">$message</p>";
  2193. $id = ($container === '') ? "ezuser-$type" : "$container-$type";
  2194. $onClick = 'ezUser.click';
  2195. return <<<HTML
  2196. <div id="$id" class="ezuser-$type ezuser-$styleFloat" onclick="$onClick(this)">$message</div>
  2197. HTML;
  2198. }
  2199. // ---------------------------------------------------------------------------
  2200. // Text versions of status and result codes
  2201. // ---------------------------------------------------------------------------
  2202. private static /*.string.*/ function statusText(/*.int.*/ $status, $more = '', $sendToBrowser = false) {
  2203. switch ($status) {
  2204. case self::STATUS_UNKNOWN: $text = "Unknown status"; break;
  2205. case self::STATUS_PENDING: $text = "Awaiting confirmation"; break;
  2206. case self::STATUS_CONFIRMED: $text = "Confirmed and active"; break;
  2207. case self::STATUS_INACTIVE: $text = "Inactive"; break;
  2208. default: $text = "Unknown status code"; break;
  2209. }
  2210. if ($more !== '') $text .= ": $more";
  2211. if ($sendToBrowser) {self::sendContent($text); return '';} else return $text;
  2212. }
  2213. private static /*.string.*/ function statusDescription(/*.int.*/ $status, $more = '', $sendToBrowser = false) {
  2214. switch ($status) {
  2215. case self::STATUS_PENDING: $text = "Your account has been created and a confirmation email has been sent. Please click on the link in the confirmation email to verify your account.";
  2216. break;
  2217. default: $text = self::statusText($status); break;
  2218. }
  2219. if ($more !== '') $text .= ": $more";
  2220. if ($sendToBrowser) {self::sendContent($text); return '';} else return $text;
  2221. }
  2222. private static /*.string.*/ function resultDescription(/*.int.*/ $result, $more = '', $sendToBrowser = false) {
  2223. switch ($result) {
  2224. case self::RESULT_EMAILFORMATERR: $text = "The format of the email address you entered was incorrect. Email addresses should be in the form <em>joe.smith@example.com</em>";
  2225. break;
  2226. default: $text = self::resultText($result); break;
  2227. }
  2228. if ($more !== '') $text .= ": $more";
  2229. if ($sendToBrowser) {self::sendContent($text); return '';} else return $text;
  2230. }
  2231. // ---------------------------------------------------------------------------
  2232. // HTML for UI Forms
  2233. // ---------------------------------------------------------------------------
  2234. /**
  2235. * Render the HTML for the account maintenance form
  2236. *
  2237. * $newUser indicates whether this is an existing user from the
  2238. * database, or a new registration that we are processing. If the user
  2239. * enters invalid data we might render this form a number of times
  2240. * until validation is successful. $newUser should persist until
  2241. * registration is successful.
  2242. *
  2243. * The form can also operate as a "wizard" (with Next and Back buttons). This
  2244. * allows it to work in the confined space of the ezUser control panel
  2245. *
  2246. * This function is also driven by the mode parameter as follows:
  2247. *
  2248. * Mode Behaviour
  2249. * ------- --------------------------------------------------------------
  2250. * - (none) Infer mode from current ezUser object - if it's authenticated
  2251. * then display the account page for that user. If not then
  2252. * display a registration form for a new user. Inferred mode will
  2253. * be 'display' or 'new'
  2254. *
  2255. * - new Register a new user. Input controls are blank but available.
  2256. * Button says Register.
  2257. *
  2258. * - display View account details. Input controls are populated but
  2259. * unavailable. Button says Edit.
  2260. *
  2261. * - edit Edit an existing account or correct a failed registration.
  2262. * Input controls are populated with existing data. Buttons say
  2263. * OK and Cancel
  2264. *
  2265. * - result Infer actual mode from result of validation. If validated then
  2266. * display account details, otherwise allow them to be corrected.
  2267. * Inferred mode will be either 'display' or 'edit'.
  2268. *
  2269. * - cancel Infer actual mode from $newUser. If we're cancelling a new
  2270. * registration then clear the form. If we're cancelling editing
  2271. * an existing user then redisplay details from the database.
  2272. * Inferred mode will be either 'new' or 'display'.
  2273. *
  2274. * So, the difference between $mode = 'new' and $newUser = true is as
  2275. * follows:
  2276. *
  2277. * - $mode = 'new' means this is a blank form for a new registration
  2278. *
  2279. * - $newUser = true means we are processing a new registration but we
  2280. * might be asking the user to re-enter certain values:
  2281. * the form might therefore need to be populated with the
  2282. * attempted registration details.
  2283. *
  2284. * @param string $mode See above
  2285. * @param boolean $newUser Is this a new or existing user?
  2286. * @param boolean $wizard Display as a wizard within control panel
  2287. * @param boolean $sendToBrowser Send HTML to browser?
  2288. */
  2289. private static /*.string.*/ function htmlAccountForm($mode = '', $newUser = false, $wizard = false, $sendToBrowser = false) {
  2290. /* Comment out profiling statements if not needed
  2291. global $ezuser_profile;
  2292. $ezuser_profile[self::ACTION_ACCOUNT . '-start'] = ezuser_time();
  2293. */
  2294. $action = self::ACTION_ACCOUNT;
  2295. $actionResend = self::ACTION_RESEND;
  2296. $actionValidate = self::ACTION_VALIDATE;
  2297. $accountForm = self::getInstanceId($action);
  2298. $container = ($wizard) ? 'ezuser' : $accountForm;
  2299. $tagFirstName = self::TAGNAME_FIRSTNAME;
  2300. $tagLastName = self::TAGNAME_LASTNAME;
  2301. $tagEmail = self::TAGNAME_EMAIL;
  2302. $tagUsername = self::TAGNAME_USERNAME;
  2303. $tagPassword = self::TAGNAME_PASSWORD;
  2304. $tagConfirm = self::TAGNAME_CONFIRM;
  2305. $tagNewUser = self::TAGNAME_NEWUSER;
  2306. $tagUseSavedPassword = self::TAGNAME_SAVEDPASSWORD;
  2307. $tagWizard = self::TAGNAME_WIZARD;
  2308. $modeNew = self::ACCOUNT_MODE_NEW;
  2309. $modeEdit = self::ACCOUNT_MODE_EDIT;
  2310. $modeDisplay = self::ACCOUNT_MODE_DISPLAY;
  2311. $modeResult = self::ACCOUNT_MODE_RESULT;
  2312. $modeCancel = self::ACCOUNT_MODE_CANCEL;
  2313. $stringRight = self::STRING_RIGHT;
  2314. $htmlButtonAction = self::htmlButton(self::BUTTON_TYPE_ACTION);
  2315. $htmlButtonHidden = self::htmlButton(self::BUTTON_TYPE_HIDDEN);
  2316. $passwordOnFocus = 'ezUser.passwordFocus';
  2317. $passwordOnBlur = 'ezUser.passwordBlur';
  2318. $htmlInputText = self::htmlInputText();
  2319. $messageShort = self::htmlMessage('* reqd', self::MESSAGE_STYLE_PLAIN, $accountForm);
  2320. $resendButton = '';
  2321. if (!isset($mode) || empty($mode)) $mode = '';
  2322. $modeInfo = ($newUser) ? self::STRING_TRUE : self::STRING_FALSE;
  2323. $modeInfo = "(originally mode was '$mode', new flag was $modeInfo) -->";
  2324. if ($mode === '') {
  2325. $ezUser = self::getSessionObject();
  2326. $result = self::RESULT_SUCCESS;
  2327. if ($ezUser->authenticated()) {
  2328. $mode = $modeDisplay;
  2329. $ezUser->addSignOutAction($action);
  2330. self::setSessionObject($ezUser, $action);
  2331. } else {
  2332. $mode = $modeNew;
  2333. $ezUser->clearSignOutActions();
  2334. }
  2335. } else {
  2336. $ezUser = self::getSessionObject($action);
  2337. $result = $ezUser->result();
  2338. }
  2339. if ($mode === $modeCancel) $ezUser->clearErrors();
  2340. // Some raw logic - think carefully about these lines before amending
  2341. if (!isset($newUser)) $newUser = false;
  2342. if ($mode === $modeNew) $newUser = true;
  2343. if ($mode === $modeCancel) $mode = ($newUser) ? $modeNew : $modeDisplay;
  2344. if ($mode === $modeResult) $mode = ($result === self::RESULT_SUCCESS) ? $modeDisplay : $modeEdit;
  2345. switch ($mode) {
  2346. case self::ACCOUNT_MODE_NEW:
  2347. $email = '';
  2348. $firstName = '';
  2349. $lastName = '';
  2350. $username = '';
  2351. $password = '';
  2352. $buttonId = $actionValidate;
  2353. $buttonText = 'Register';
  2354. $buttonAction = $actionValidate;
  2355. $disabled = '';
  2356. $htmlOtherButton = "\t\t\t\t<input id=\"$accountForm-$modeCancel\" data-ezuser-action=\"$action=$modeCancel\" value=\"Cancel\"\n\t\t\t\t\ttabindex\t=\t\"3219\"\n$htmlButtonAction\n\t\t\t\t/>\n";
  2357. $useSavedPassword = false;
  2358. $messageLong = self::htmlMessage('', self::MESSAGE_STYLE_TEXT, $accountForm, self::MESSAGE_TYPE_TEXT);
  2359. break;
  2360. case self::ACCOUNT_MODE_DISPLAY:
  2361. $errors = $ezUser->errors();
  2362. $email = (array_key_exists(self::TAGNAME_EMAIL, $errors)) ? $errors[self::TAGNAME_EMAIL] : $ezUser->email();
  2363. $firstName = (array_key_exists(self::TAGNAME_FIRSTNAME, $errors)) ? $errors[self::TAGNAME_FIRSTNAME] : $ezUser->firstName();
  2364. $lastName = (array_key_exists(self::TAGNAME_LASTNAME, $errors)) ? $errors[self::TAGNAME_LASTNAME] : $ezUser->lastName();
  2365. $username = (array_key_exists(self::TAGNAME_USERNAME, $errors)) ? $errors[self::TAGNAME_USERNAME] : $ezUser->username();
  2366. $password = ($ezUser->passwordHash() === '') ? '' : self::PASSWORD_MASK;
  2367. $buttonId = $modeEdit;
  2368. $buttonText = 'Edit';
  2369. $buttonAction = "$action=$modeEdit";
  2370. $disabled = "\t\t\t\t\tdisabled\t=\t\"disabled\"\r\n";
  2371. $htmlOtherButton = "\t\t\t\t<input id=\"$accountForm-$modeNew\" data-ezuser-action=\"$action=$modeNew\" value=\"New\"\n\t\t\t\t\ttabindex\t=\t\"3219\"\n$htmlButtonAction\n\t\t\t\t/>\n";
  2372. $useSavedPassword = false;
  2373. $newUser = false;
  2374. if ($result === self::RESULT_SUCCESS || $result === self::RESULT_UNDEFINED) {
  2375. // Show status information
  2376. $status = $ezUser->status();
  2377. $messageLong = ($status === self::STATUS_CONFIRMED) ? '' : self::statusDescription($status);
  2378. $messageLong = self::htmlMessage($messageLong, self::MESSAGE_STYLE_TEXT, $accountForm, self::MESSAGE_TYPE_TEXT);
  2379. if ($status === self::STATUS_PENDING) $resendButton = "\n\t\t\t\t<input id=\"$accountForm-$actionResend\" data-ezuser-action=\"$actionResend\" value=\"Resend\"\n\t\t\t\t\ttabindex\t=\t\"3219\"\n$htmlButtonAction\n\t\t\t\t/>";
  2380. } else {
  2381. // Show result information
  2382. $messageLong = self::resultDescription($result);
  2383. $messageLong = self::htmlMessage($messageLong, self::MESSAGE_STYLE_FAIL, $accountForm, self::MESSAGE_TYPE_TEXT);
  2384. }
  2385. break;
  2386. case self::ACCOUNT_MODE_EDIT:
  2387. $errors = $ezUser->errors();
  2388. $email = (array_key_exists(self::TAGNAME_EMAIL, $errors)) ? $errors[self::TAGNAME_EMAIL] : $ezUser->email();
  2389. $firstName = (array_key_exists(self::TAGNAME_FIRSTNAME, $errors)) ? $errors[self::TAGNAME_FIRSTNAME] : $ezUser->firstName();
  2390. $lastName = (array_key_exists(self::TAGNAME_LASTNAME, $errors)) ? $errors[self::TAGNAME_LASTNAME] : $ezUser->lastName();
  2391. $username = (array_key_exists(self::TAGNAME_USERNAME, $errors)) ? $errors[self::TAGNAME_USERNAME] : $ezUser->username();
  2392. $password = ($ezUser->passwordHash() === '') ? '' : self::PASSWORD_MASK;
  2393. $buttonId = $actionValidate;
  2394. $buttonText = 'OK';
  2395. $buttonAction = $actionValidate;
  2396. $disabled = '';
  2397. $htmlOtherButton = "\t\t\t\t<input id=\"$accountForm-$modeCancel\" data-ezuser-action=\"$action=$modeCancel\" value=\"Cancel\"\n\t\t\t\t\ttabindex\t=\t\"3219\"\n$htmlButtonAction\n\t\t\t\t/>\n";
  2398. $useSavedPassword = $newUser;
  2399. if ($result === self::RESULT_SUCCESS || $result === self::RESULT_UNDEFINED) {
  2400. $messageLong = self::htmlMessage('', self::MESSAGE_STYLE_TEXT, $accountForm, self::MESSAGE_TYPE_TEXT);
  2401. } else {
  2402. // Show result information
  2403. $messageLong = self::resultDescription($result);
  2404. $messageLong = self::htmlMessage($messageLong, self::MESSAGE_STYLE_FAIL, $accountForm, self::MESSAGE_TYPE_TEXT);
  2405. }
  2406. break;
  2407. default:
  2408. $useSavedPassword = false;
  2409. $email = '';
  2410. $disabled = '';
  2411. $firstName = '';
  2412. $lastName = '';
  2413. $username = '';
  2414. $password = '';
  2415. $buttonId = '';
  2416. $buttonAction = '';
  2417. $buttonText = '';
  2418. $htmlOtherButton = '';
  2419. $messageLong = '';
  2420. break;
  2421. }
  2422. // Some hidden form elements
  2423. $newString = ($newUser) ? self::STRING_TRUE : self::STRING_FALSE;
  2424. $useSavedPasswordString = ($useSavedPassword) ? self::STRING_TRUE : self::STRING_FALSE;
  2425. $modeInfo = "<!-- Mode is '$mode', new flag is $newString $modeInfo";
  2426. // Form varies slightly if it's working in wizard mode
  2427. if ($wizard) {
  2428. $wizardString = self::STRING_TRUE;
  2429. $styleHidden = ' ezuser-hidden';
  2430. $htmlNavigation = <<<HTML
  2431. <input id="$accountForm-next" data-ezuser-action="next" value="Next &gt;"
  2432. tabindex = "3218"
  2433. $htmlButtonAction
  2434. />
  2435. <input id="$accountForm-back" data-ezuser-action="back" value="&lt; Back"
  2436. tabindex = "3217"
  2437. $htmlButtonHidden
  2438. />
  2439. HTML;
  2440. } else {
  2441. $wizardString = self::STRING_FALSE;
  2442. $styleHidden = '';
  2443. $htmlNavigation = '';
  2444. }
  2445. // The lower two fieldsets are transposed if we're in wizard mode
  2446. $messageFieldset = <<<HTML
  2447. <fieldset id="$accountForm-fieldset-3" class="ezuser-fieldset$styleHidden">
  2448. $messageLong$resendButton
  2449. <input id="$accountForm-$tagNewUser" type="hidden" value="$newString" />
  2450. <input id="$accountForm-$tagWizard" type="hidden" value="$wizardString" />
  2451. <input id="$accountForm-$tagUseSavedPassword" type="hidden" value="$useSavedPasswordString" />
  2452. </fieldset>
  2453. HTML;
  2454. $buttonsFieldset = <<<HTML
  2455. <fieldset class="ezuser-fieldset">
  2456. $messageShort
  2457. <input id="$accountForm-$buttonId" data-ezuser-action="$buttonAction" value="$buttonText"
  2458. tabindex = "3220"
  2459. $htmlButtonAction
  2460. />
  2461. $htmlOtherButton$htmlNavigation </fieldset>
  2462. HTML;
  2463. $bottomFieldsets = ($wizard) ? "$messageFieldset$buttonsFieldset" : "$buttonsFieldset$messageFieldset";
  2464. // At this point we have finished with the result of any prior validation
  2465. // so we can clear the result field
  2466. $ezUser->setResult(self::RESULT_UNDEFINED);
  2467. $html = <<<HTML
  2468. $modeInfo
  2469. <form id="$accountForm-form" class="ezuser-form" onsubmit="return false">
  2470. <fieldset id="$accountForm-fieldset-1" class="ezuser-fieldset">
  2471. <input id= "$accountForm-$tagEmail"
  2472. tabindex = "3211"
  2473. value = "$email"
  2474. type = "text"
  2475. $disabled$htmlInputText
  2476. />
  2477. <label class="ezuser-label ezuser-$stringRight" for="$accountForm-$tagEmail">* Email address:</label>
  2478. <input id= "$accountForm-$tagFirstName"
  2479. tabindex = "3212"
  2480. value = "$firstName"
  2481. type = "text"
  2482. $disabled$htmlInputText
  2483. />
  2484. <label class="ezuser-label ezuser-$stringRight" for="$accountForm-$tagFirstName">First name:</label>
  2485. <input id= "$accountForm-$tagLastName"
  2486. tabindex = "3213"
  2487. value = "$lastName"
  2488. type = "text"
  2489. $disabled$htmlInputText
  2490. />
  2491. <label class="ezuser-label ezuser-$stringRight" for="$accountForm-$tagLastName">Last name:</label>
  2492. </fieldset>
  2493. <fieldset id="$accountForm-fieldset-2" class="ezuser-fieldset$styleHidden">
  2494. <input id= "$accountForm-$tagUsername"
  2495. tabindex = "3214"
  2496. value = "$username"
  2497. type = "text"
  2498. onkeypress = "return ezUser.keyPress(event)"
  2499. $disabled$htmlInputText
  2500. />
  2501. <label class="ezuser-label ezuser-$stringRight" for="$accountForm-$tagUsername">* Username:</label>
  2502. <input id= "$accountForm-$tagPassword"
  2503. tabindex = "3215"
  2504. value = "$password"
  2505. type = "password"
  2506. onfocus = "$passwordOnFocus(this)"
  2507. onblur = "$passwordOnBlur(this)"
  2508. $disabled$htmlInputText
  2509. />
  2510. <label class="ezuser-label ezuser-$stringRight" for="$accountForm-$tagPassword">* Password:</label>
  2511. <input id= "$accountForm-confirm"
  2512. tabindex = "3216"
  2513. value = "$password"
  2514. type = "password"
  2515. onfocus = "$passwordOnFocus(this)"
  2516. onblur = "$passwordOnBlur(this)"
  2517. $disabled$htmlInputText
  2518. />
  2519. <label class="ezuser-label ezuser-$stringRight" for="$accountForm-$tagConfirm">* Confirm password:</label>
  2520. </fieldset>
  2521. $bottomFieldsets </form>
  2522. HTML;
  2523. /* Comment out profiling statements if not needed
  2524. $ezuser_profile[self::ACTION_ACCOUNT . '-end'] = ezuser_time();
  2525. */
  2526. if ($sendToBrowser) {self::sendXML($html, $container); return '';} else return $html;
  2527. }
  2528. // ---------------------------------------------------------------------------
  2529. private static /*.string.*/ function htmlDashboard($sendToBrowser = false) {
  2530. $action = self::ACTION_DASHBOARD;
  2531. $actionSignOut = self::ACTION_SIGNOUT;
  2532. $actionAccountForm = self::ACTION_ACCOUNTFORM;
  2533. $tagFullName = self::TAGNAME_FULLNAME;
  2534. $htmlButtonPreference = self::htmlButton(self::BUTTON_TYPE_PREFERENCE);
  2535. $message = self::htmlMessage();
  2536. $ezUser = self::getSessionObject();
  2537. $fullName = $ezUser->fullName();
  2538. $html = <<<HTML
  2539. <form id="ezuser-$action-form" class="ezuser-form" onsubmit="return false">
  2540. <fieldset class="ezuser-fieldset">
  2541. <input id="ezuser-$actionSignOut" data-ezuser-action="$actionSignOut" value="Sign out"
  2542. tabindex = "3222"
  2543. $htmlButtonPreference
  2544. />
  2545. <input id="ezuser-$actionAccountForm" data-ezuser-action="$actionAccountForm" value="My account"
  2546. tabindex = "3221"
  2547. $htmlButtonPreference
  2548. />
  2549. <div id="ezuser-$tagFullName" class="ezuser-$tagFullName">$fullName</div>
  2550. </fieldset>
  2551. <fieldset class="ezuser-fieldset">
  2552. $message
  2553. </fieldset>
  2554. </form>
  2555. HTML;
  2556. if ($sendToBrowser) {self::sendXML($html); return '';} else return $html;
  2557. }
  2558. // ---------------------------------------------------------------------------
  2559. private static /*.string.*/ function htmlSignInForm($username = '', $sendToBrowser = false) {
  2560. $verbose = false; // Set to true to let the user see detailed result information (recommended setting is false)
  2561. //$verbose = true; // debug
  2562. $action = self::ACTION_SIGNIN;
  2563. $actionAccountForm = self::ACTION_ACCOUNTFORM;
  2564. $actionResetRequest = self::ACTION_RESETREQUEST;
  2565. $actionBitmap = self::ACTION_BITMAP;
  2566. $tagUsername = self::TAGNAME_USERNAME;
  2567. $tagPassword = self::TAGNAME_PASSWORD;
  2568. $tagRememberMe = self::TAGNAME_REMEMBERME;
  2569. $tagStaySignedIn = self::TAGNAME_STAYSIGNEDIN;
  2570. $tagVerbose = self::TAGNAME_VERBOSE;
  2571. $stringRight = self::STRING_RIGHT;
  2572. $htmlButtonAction = self::htmlButton(self::BUTTON_TYPE_ACTION);
  2573. $htmlButtonPreference = self::htmlButton(self::BUTTON_TYPE_PREFERENCE);
  2574. $passwordOnFocus = 'ezUser.passwordFocus';
  2575. $passwordOnBlur = 'ezUser.passwordBlur';
  2576. $htmlInputText = self::htmlInputText();
  2577. $ezUser = self::getSessionObject();
  2578. $result = $ezUser->result();
  2579. $URL = self::thisURL();
  2580. if ($result <= self::RESULT_SUCCESS) {
  2581. $message = self::htmlMessage();
  2582. $verboseHTML = "";
  2583. } else {
  2584. $ezUser->setResult(self::RESULT_UNDEFINED);
  2585. $username = $ezUser->username();
  2586. $message = self::htmlMessage("Check username &amp; password", self::MESSAGE_STYLE_FAIL);
  2587. if ($verbose) {
  2588. $verboseHTML = self::htmlButton(self::BUTTON_TYPE_PREFERENCE, $stringRight, true);
  2589. $verboseHTML = <<<HTML
  2590. <input id="ezuser-$tagVerbose" value="$result"
  2591. $verboseHTML
  2592. />
  2593. HTML;
  2594. } else {
  2595. $verboseHTML = '';
  2596. }
  2597. }
  2598. $password = '';
  2599. $html = <<<HTML
  2600. <img id="ezuser-close" class="ezuser-dialog-control" src="$URL?$actionBitmap=close" onclick="ezUser.click(this)" />
  2601. <form id="ezuser-$action-form" class="ezuser-form" onsubmit="return false">
  2602. <fieldset class="ezuser-fieldset">
  2603. <input id= "ezuser-$tagUsername"
  2604. tabindex = "3201"
  2605. value = "$username"
  2606. type = "text"
  2607. $htmlInputText
  2608. />
  2609. <label class="ezuser-label ezuser-$stringRight" for="ezuser-$tagUsername">Username:</label>
  2610. <input id= "ezuser-$tagPassword"
  2611. tabindex = "3202"
  2612. value = "$password"
  2613. type = "password"
  2614. onfocus = "$passwordOnFocus(this)"
  2615. onblur = "$passwordOnBlur(this)"
  2616. $htmlInputText
  2617. />
  2618. <label class="ezuser-label ezuser-$stringRight" for="ezuser-$tagPassword">Password:</label>
  2619. $verboseHTML </fieldset>
  2620. <fieldset class="ezuser-fieldset">
  2621. $message
  2622. <input id="ezuser-$actionAccountForm" data-ezuser-action="$actionAccountForm" value="Register"
  2623. tabindex = "3204"
  2624. $htmlButtonAction
  2625. />
  2626. <input id="ezuser-$action" data-ezuser-action="$action" value="Sign in"
  2627. tabindex = "3203"
  2628. $htmlButtonAction
  2629. />
  2630. </fieldset>
  2631. <fieldset class="ezuser-fieldset">
  2632. <input id="ezuser-$tagStaySignedIn" value="Stay signed in"
  2633. tabindex = "3207"
  2634. $htmlButtonPreference
  2635. />
  2636. <input id="ezuser-$tagRememberMe" value="Remember me"
  2637. tabindex = "3206"
  2638. $htmlButtonPreference
  2639. />
  2640. <input id="ezuser-$actionResetRequest" data-ezuser-action="$actionResetRequest" value="Reset password"
  2641. tabindex = "3205"
  2642. $htmlButtonPreference
  2643. />
  2644. </fieldset>
  2645. </form>
  2646. HTML;
  2647. if ($sendToBrowser) {self::sendXML($html); return '';} else return $html;
  2648. }
  2649. // ---------------------------------------------------------------------------
  2650. private static /*.string.*/ function htmlControlPanel($username = '', $sendToBrowser = false) {
  2651. $ezUser = self::getSessionObject();
  2652. $html = ($ezUser->authenticated()) ? self::htmlDashboard() : self::htmlSignInForm($username);
  2653. if ($sendToBrowser) {self::sendXML($html); return '';} else return $html;
  2654. }
  2655. // ---------------------------------------------------------------------------
  2656. private static /*.string.*/ function htmlResetRequest ($username = '', $sendToBrowser = false) {
  2657. $action = self::ACTION_RESETREQUEST;
  2658. $actionCancel = self::ACTION_CANCEL;
  2659. $actionResetPassword = self::ACTION_RESETPASSWORD;
  2660. $actionMain = self::ACTION_MAIN;
  2661. $tagUsername = self::TAGNAME_USERNAME;
  2662. $htmlButtonPreference = self::htmlButton(self::BUTTON_TYPE_PREFERENCE);
  2663. $stringLeft = self::STRING_LEFT;
  2664. $htmlInputText = self::htmlInputText($stringLeft);
  2665. $html = <<<HTML
  2666. <form id="ezuser-$action-form" class="ezuser-form" onsubmit="return false">
  2667. <fieldset class="ezuser-fieldset-float">
  2668. <label class="ezuser-label ezuser-$stringLeft" for="ezuser-$tagUsername">Username or email address:</label>
  2669. <input style="clear:both;" id="ezuser-$tagUsername"
  2670. tabindex = "3241"
  2671. value = "$username"
  2672. type = "text"
  2673. $htmlInputText
  2674. />
  2675. </fieldset>
  2676. <fieldset class="ezuser-fieldset">
  2677. <input id="ezuser-$actionCancel" data-ezuser-action="$actionMain" value="Cancel"
  2678. tabindex = "3243"
  2679. $htmlButtonPreference
  2680. />
  2681. <input id="ezuser-$actionResetPassword" data-ezuser-action="$actionResetPassword" value="Reset password"
  2682. tabindex = "3242"
  2683. $htmlButtonPreference
  2684. />
  2685. </fieldset>
  2686. </form>
  2687. HTML;
  2688. if ($sendToBrowser) {self::sendXML($html); return '';} else return $html;
  2689. }
  2690. /**
  2691. * Password reset form (& confirmation form below)
  2692. *
  2693. * This function is slightly different as it send the HTML for an entire
  2694. * page, rather than the contents of a DIV. This is because this form
  2695. * is displayed in response to the user clicking on a link in an email.
  2696. * We have no context in which to display it and no knowledge of the
  2697. * site that ezUser is living in, so we are forced to display a bare page.
  2698. *
  2699. * @param boolean $sendToBrowser
  2700. */
  2701. private static /*.string.*/ function htmlResetPassword (ezUser_base $ezUser, $sendToBrowser = false) {
  2702. $action = self::ACTION_RESET;
  2703. $tagPassword = self::TAGNAME_PASSWORD;
  2704. $tagConfirm = self::TAGNAME_CONFIRM;
  2705. $container = self::getInstanceId($action);
  2706. $htmlInputText = self::htmlInputText();
  2707. $htmlButtonPreference = self::htmlButton(self::BUTTON_TYPE_ACTION);
  2708. $stringRight = self::STRING_RIGHT;
  2709. $passwordOnFocus = 'ezUser.passwordFocus';
  2710. $passwordOnBlur = 'ezUser.passwordBlur';
  2711. $fullName = $ezUser->fullName();
  2712. $message = self::htmlMessage('', self::MESSAGE_STYLE_PLAIN, 'ezuser', self::MESSAGE_TYPE_TEXT);
  2713. $html = <<<HTML
  2714. <div id="ezuser">
  2715. <h4 class="ezuser-heading">Welcome $fullName</h4>
  2716. <p class="ezuser-message-plain">Please enter a new password for your account:</p>
  2717. <form id="$container-form" class="ezuser-form" onsubmit="return false">
  2718. <fieldset class="ezuser-fieldset">
  2719. <input id= "$container-$tagPassword"
  2720. tabindex = "3241"
  2721. value = ""
  2722. type = "password"
  2723. onfocus = "$passwordOnFocus(this)"
  2724. onblur = "$passwordOnBlur(this)"
  2725. $htmlInputText
  2726. />
  2727. <label class="ezuser-label ezuser-$stringRight" for="$container-$tagPassword">Password:</label>
  2728. <input id= "$container-confirm"
  2729. tabindex = "3242"
  2730. value = ""
  2731. type = "password"
  2732. onfocus = "$passwordOnFocus(this)"
  2733. onblur = "$passwordOnBlur(this)"
  2734. $htmlInputText
  2735. />
  2736. <label class="ezuser-label ezuser-$stringRight" for="$container-$tagConfirm">Confirm password:</label>
  2737. </fieldset>
  2738. <fieldset class="ezuser-fieldset">
  2739. <input id="$container-OK" data-ezuser-action="$action" value="OK"
  2740. tabindex = "3243"
  2741. $htmlButtonPreference
  2742. />
  2743. </fieldset>
  2744. <fieldset class="ezuser-fieldset">
  2745. $message
  2746. </fieldset>
  2747. </form>
  2748. </div>
  2749. HTML;
  2750. $html = self::htmlPage($html, 'Reset your password');
  2751. if ($sendToBrowser) {self::sendContent($html); return '';} else return $html;
  2752. }
  2753. private static /*.string.*/ function htmlMessagePage (/*.string.*/ $title, /*.string.*/ $message, $sendToBrowser = false) {
  2754. $message = self::htmlMessage($message, self::MESSAGE_STYLE_PLAIN, 'ezuser', self::MESSAGE_TYPE_TEXT);
  2755. $html = <<<HTML
  2756. <div id="ezuser">
  2757. <h4 class="ezuser-heading">$title</h4>
  2758. $message
  2759. </div>
  2760. HTML;
  2761. $html = self::htmlPage($html, $title);
  2762. if ($sendToBrowser) {self::sendContent($html); return '';} else return $html;
  2763. }
  2764. // ---------------------------------------------------------------------------
  2765. private static /*.string.*/ function htmlMessageForm ($message = '', $action = self::ACTION_MAIN, $sendToBrowser = false) {
  2766. $actionMain = self::ACTION_MAIN;
  2767. $htmlButtonPreference = self::htmlButton(self::BUTTON_TYPE_PREFERENCE);
  2768. $message = self::htmlMessage($message, self::MESSAGE_STYLE_TEXT, '', self::MESSAGE_TYPE_TEXT);
  2769. $html = <<<HTML
  2770. <form id="ezuser-$action-form" class="ezuser-form" onsubmit="return false">
  2771. <fieldset class="ezuser-fieldset">
  2772. $message
  2773. <input id="ezuser-OK" data-ezuser-action="$actionMain" value="OK"
  2774. tabindex = "3241"
  2775. $htmlButtonPreference
  2776. />
  2777. </fieldset>
  2778. </form>
  2779. HTML;
  2780. if ($sendToBrowser) {self::sendXML($html); return '';} else return $html;
  2781. }
  2782. private static /*.string.*/ function htmlResultForm (/*.int.*/ $result, $more = '', $sendToBrowser = false) {
  2783. $html = self::htmlMessageForm(self::resultText($result, $more), self::ACTION_RESULTFORM);
  2784. if ($sendToBrowser) {self::sendXML($html); return '';} else return $html;
  2785. }
  2786. // ---------------------------------------------------------------------------
  2787. private static /*.string.*/ function htmlAboutText($sendToBrowser = false) {
  2788. $php = self::getFileContents('ezuser.php', 0, NULL, -1, 4096);
  2789. $html = self::docBlock_to_HTML($php);
  2790. if ($sendToBrowser) {self::sendContent($html); return '';} else return $html;
  2791. }
  2792. // ---------------------------------------------------------------------------
  2793. private static /*.string.*/ function htmlAbout($sendToBrowser = false) {
  2794. $html = self::htmlPage(self::htmlAboutText(), 'ezUser - About');
  2795. if ($sendToBrowser) {self::sendContent($html); return '';} else return $html;
  2796. }
  2797. // ---------------------------------------------------------------------------
  2798. private static /*.string.*/ function htmlSourceCode($sendToBrowser = false) {
  2799. $html = (string) highlight_file(__FILE__, 1);
  2800. if ($sendToBrowser) {self::sendContent($html); return '';} else return $html;
  2801. }
  2802. /**
  2803. * Other MIME types: CSS, Javascript, bitmaps
  2804. */
  2805. private static /*.string.*/ function htmlStyleSheet(/*.boolean.*/ $sendToBrowser = false) {
  2806. $container = self::getInstanceId(self::ACTION_ACCOUNT);
  2807. $tagFullName = self::TAGNAME_FULLNAME;
  2808. $tagVerbose = self::TAGNAME_VERBOSE;
  2809. $buttonTypeAction = self::BUTTON_TYPE_ACTION;
  2810. $buttonTypePreference = self::BUTTON_TYPE_PREFERENCE;
  2811. $css = <<<GENERATED
  2812. @charset "UTF-8";
  2813. /**
  2814. * Enables user registration and authentication for a website
  2815. *
  2816. * This code has three principle design goals:
  2817. *
  2818. * 1. To make it easy for people to register and sign in to your site.
  2819. * 2. To make it easy for you to add this functionality to your site.
  2820. * 3. To make it easy for you to administer the user database on your site.
  2821. *
  2822. * Other design goals, such as run-time efficiency, are important but secondary to
  2823. * these.
  2824. *
  2825. * Copyright (c) 2008-2010, Dominic Sayers <br>
  2826. * All rights reserved.
  2827. *
  2828. * Redistribution and use in source and binary forms, with or without modification,
  2829. * are permitted provided that the following conditions are met:
  2830. *
  2831. * - Redistributions of source code must retain the above copyright notice,
  2832. * this list of conditions and the following disclaimer.
  2833. * - Redistributions in binary form must reproduce the above copyright notice,
  2834. * this list of conditions and the following disclaimer in the documentation
  2835. * and/or other materials provided with the distribution.
  2836. * - Neither the name of Dominic Sayers nor the names of its contributors may be
  2837. * used to endorse or promote products derived from this software without
  2838. * specific prior written permission.
  2839. *
  2840. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  2841. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  2842. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  2843. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  2844. * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  2845. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  2846. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  2847. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  2848. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  2849. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  2850. *
  2851. * @package ezUser
  2852. * @author Dominic Sayers <dominic@sayers.cc>
  2853. * @copyright 2008-2010 Dominic Sayers
  2854. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  2855. * @link http://code.google.com/p/ezuser/
  2856. * @version 0.25.5 - Floating div dialog boxes
  2857. */
  2858. .dummy {} /* Webkit is ignoring the first item so we'll put a dummy one in */
  2859. .ezuser {
  2860. margin:0;
  2861. padding:0;
  2862. font-family:"Segoe UI",Geneva,Tahoma,Arial,Helvetica,sans-serif;
  2863. font-size:11px;
  2864. }
  2865. pre.ezuser {font-family:Consolas, Courier New, Courier, fixedsys;}
  2866. .ezuser-left {float:left;}
  2867. .ezuser-right {float:right;}
  2868. .ezuser-hidden {display:none;}
  2869. .ezuser-heading {padding:6px;margin:0 0 1em 0;}
  2870. .ezuser-dialog-control {cursor:pointer;}
  2871. img#ezuser-close {
  2872. width:40px;
  2873. height:40px;
  2874. position:absolute;
  2875. left:319px;
  2876. top:-25px;
  2877. }
  2878. div#ezuser {
  2879. text-align: left;
  2880. width: 300px;
  2881. height: 100px;
  2882. font-family:"Segoe UI",Geneva,Tahoma,Arial,Helvetica,sans-serif;
  2883. font-size:11px;
  2884. padding:1.5em;
  2885. border:1em solid #CCCCCC;
  2886. border-radius: 2em;
  2887. -icab-border-radius: 2em; /* iCab */
  2888. -khtml-border-radius: 2em; /* Konqueror */
  2889. -moz-border-radius: 2em; /* Firefox */
  2890. -o-border-radius: 2em; /* Opera */
  2891. -webkit-border-radius: 2em; /* Chrome, Safari */
  2892. box-shadow: 0.4em 0.4em 1.8em #555555;
  2893. -icab-box-shadow: 0.4em 0.4em 1.8em #555555; /* iCab */
  2894. -khtml-box-shadow: 0.4em 0.4em 1.8em #555555; /* Konqueror */
  2895. -moz-box-shadow: 0.4em 0.4em 1.8em #555555; /* Firefox */
  2896. -o-box-shadow: 0.4em 0.4em 1.8em #555555; /* Opera */
  2897. -webkit-box-shadow: 0.4em 0.4em 1.8em #555555; /* Chrome, Safari */
  2898. line-height:100%;
  2899. background-color:#EEEEEE;
  2900. }
  2901. div#$container {
  2902. font-family:"Segoe UI",Geneva,Tahoma,Arial,Helvetica,sans-serif;
  2903. font-size:12px;
  2904. line-height:100%;
  2905. float:left;
  2906. }
  2907. div.ezuser-message {
  2908. float:left;
  2909. text-align:center;
  2910. font-weight:normal;
  2911. }
  2912. div.ezuser-text {
  2913. width:286px;
  2914. float:left;
  2915. padding:0;
  2916. text-align:justify;
  2917. margin:7px 0 7px 0;
  2918. line-height:16px;
  2919. }
  2920. p.ezuser-message-plain {margin:0;padding:6px;}
  2921. p.ezuser-message-info {margin:0;padding:6px;background-color:#FFCC00;color:#000000;}
  2922. p.ezuser-message-text {margin:0;padding:6px;background-color:#EEEEEE;color:#000000;}
  2923. p.ezuser-message-fail {margin:0;padding:6px;background-color:#FF0000;color:#FFFFFF;font-weight:bold;}
  2924. p.ezuser-message-hidden {display:none;}
  2925. div.ezuser-$tagFullName {
  2926. float:right;
  2927. margin:4px 0 0 0;
  2928. padding:6px;
  2929. color:#555555;
  2930. font-weight:bold;
  2931. }
  2932. form.ezuser-form {margin:0;}
  2933. fieldset.ezuser-fieldset {margin:0;padding:0;border:0;clear:both;float:right;width:286px;}
  2934. fieldset.ezuser-fieldset-float {margin:0;padding:0;border:0;clear:both;float:right;}
  2935. label.ezuser-label {padding:4px;}
  2936. input.ezuser-text {
  2937. font-size:11px;
  2938. width:160px;
  2939. margin-bottom:4px;
  2940. }
  2941. input.ezuser-button {
  2942. padding:2px;
  2943. font-family:"Segoe UI",Geneva,Tahoma,Arial,Helvetica,sans-serif;
  2944. border-style:solid;
  2945. border-width:1px;
  2946. border-radius: 4px;
  2947. -icab-border-radius: 4px; /* iCab */
  2948. -khtml-border-radius: 4px; /* Konqueror */
  2949. -moz-border-radius: 4px; /* Firefox */
  2950. -o-border-radius: 4px; /* Opera */
  2951. -webkit-border-radius: 4px; /* Chrome, Safari */
  2952. cursor:pointer;
  2953. }
  2954. input.ezuser-$buttonTypeAction {
  2955. font-size:12px;
  2956. width:52px;
  2957. margin:0 0 0 6px;
  2958. }
  2959. input.ezuser-$buttonTypePreference {
  2960. font-size:10px;
  2961. margin:4px 0 0 6px;
  2962. }
  2963. input.ezuser-preference-$tagVerbose {float:left;margin:0;}
  2964. input.ezuser-buttonstate-0 {background-color:#FFFFFF;color:#444444;border-color:#666666 #333333 #333333 #666666;}
  2965. input.ezuser-buttonstate-1 {background-color:#FFFFFF;color:#444444;border-color:#FF9900 #CC6600 #CC6600 #FF9900;}
  2966. input.ezuser-buttonstate-2 {background-color:#FFFFFF;color:#444444;border-color:#666666 #333333 #333333 #666666;}
  2967. input.ezuser-buttonstate-3 {background-color:#FFFFFF;color:#444444;border-color:#FF9900 #CC6600 #CC6600 #FF9900;}
  2968. input.ezuser-buttonstate-4 {background-color:#CCCCCC;color:#222222;border-color:#333333 #666666 #666666 #333333;}
  2969. input.ezuser-buttonstate-5 {background-color:#CCCCCC;color:#222222;border-color:#CC6600 #FF9900 #FF9900 #CC6600;}
  2970. input.ezuser-buttonstate-6 {background-color:#CCCCCC;color:#222222;border-color:#333333 #666666 #666666 #333333;}
  2971. input.ezuser-buttonstate-7 {background-color:#CCCCCC;color:#222222;border-color:#CC6600 #FF9900 #FF9900 #CC6600;}
  2972. GENERATED;
  2973. // Generated code - do not modify in built package
  2974. if ($sendToBrowser) {self::sendContent($css, '', 'text/css'); return '';} else return $css;
  2975. }
  2976. // ---------------------------------------------------------------------------
  2977. private static /*.string.*/ function htmlJavascript($containerList = '', $sendToBrowser = false) {
  2978. $accountForm = self::getInstanceId(self::ACTION_ACCOUNT);
  2979. $sessionName = ini_get('session.name');
  2980. $remoteAddress = $_SERVER['REMOTE_ADDR'];
  2981. $URL = self::thisURL();
  2982. $folder = dirname($URL);
  2983. $cookieUsername = self::COOKIE_USERNAME;
  2984. $cookiePassword = self::COOKIE_PASSWORD;
  2985. $cookieStaySignedIn = self::COOKIE_AUTOSIGN;
  2986. $tagFirstName = self::TAGNAME_FIRSTNAME;
  2987. $tagLastName = self::TAGNAME_LASTNAME;
  2988. $tagEmail = self::TAGNAME_EMAIL;
  2989. $tagUsername = self::TAGNAME_USERNAME;
  2990. $tagPassword = self::TAGNAME_PASSWORD;
  2991. $tagConfirm = self::TAGNAME_CONFIRM;
  2992. $tagNewUser = self::TAGNAME_NEWUSER;
  2993. $tagRememberMe = self::TAGNAME_REMEMBERME;
  2994. $tagStaySignedIn = self::TAGNAME_STAYSIGNEDIN;
  2995. $tagUseSavedPassword = self::TAGNAME_SAVEDPASSWORD;
  2996. $tagVerbose = self::TAGNAME_VERBOSE;
  2997. $tagWizard = self::TAGNAME_WIZARD;
  2998. $action = self::ACTION;
  2999. $actionAccountForm = self::ACTION_ACCOUNTFORM;
  3000. $actionAccountWizard = self::ACTION_ACCOUNTWIZARD;
  3001. $actionValidate = self::ACTION_VALIDATE;
  3002. $actionSignIn = self::ACTION_SIGNIN;
  3003. $actionCancel = self::ACTION_CANCEL;
  3004. $actionCSS = self::ACTION_STYLESHEET;
  3005. $actionResultForm = self::ACTION_RESULTFORM;
  3006. $actionResend = self::ACTION_RESEND;
  3007. $actionReset = self::ACTION_RESET;
  3008. $actionResetPassword = self::ACTION_RESETPASSWORD;
  3009. $actionResetRequest = self::ACTION_RESETREQUEST;
  3010. $modeEdit = self::ACCOUNT_MODE_EDIT;
  3011. $messageTypeText = self::MESSAGE_TYPE_TEXT;
  3012. $delimPlus = self::DELIMITER_PLUS;
  3013. $stringRight = self::STRING_RIGHT;
  3014. $stringTrue = self::STRING_TRUE;
  3015. $stringFalse = self::STRING_FALSE;
  3016. $passwordMask = self::PASSWORD_MASK;
  3017. $accountPage = self::getSetting(self::SETTINGS_ACCOUNTPAGE);
  3018. $accountClick = ($accountPage === '') ? "ezUser.ajax.execute('$actionAccountWizard')" : "window.location = '$folder/$accountPage'";
  3019. // Append code to request container content
  3020. if ($containerList === '') {
  3021. $immediateJavascript = '';
  3022. } else {
  3023. // Space-separated list of containers to fill
  3024. $immediateJavascript = "ezUser.ajax.execute('" . (string) str_replace(self::DELIMITER_SPACE, self::DELIMITER_PLUS, $containerList) . "');";
  3025. }
  3026. $js = <<<GENERATED
  3027. /**
  3028. * Enables user registration and authentication for a website
  3029. *
  3030. * This code has three principle design goals:
  3031. *
  3032. * 1. To make it easy for people to register and sign in to your site.
  3033. * 2. To make it easy for you to add this functionality to your site.
  3034. * 3. To make it easy for you to administer the user database on your site.
  3035. *
  3036. * Other design goals, such as run-time efficiency, are important but secondary to
  3037. * these.
  3038. *
  3039. * Copyright (c) 2008-2010, Dominic Sayers <br>
  3040. * All rights reserved.
  3041. *
  3042. * Redistribution and use in source and binary forms, with or without modification,
  3043. * are permitted provided that the following conditions are met:
  3044. *
  3045. * - Redistributions of source code must retain the above copyright notice,
  3046. * this list of conditions and the following disclaimer.
  3047. * - Redistributions in binary form must reproduce the above copyright notice,
  3048. * this list of conditions and the following disclaimer in the documentation
  3049. * and/or other materials provided with the distribution.
  3050. * - Neither the name of Dominic Sayers nor the names of its contributors may be
  3051. * used to endorse or promote products derived from this software without
  3052. * specific prior written permission.
  3053. *
  3054. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  3055. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  3056. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  3057. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  3058. * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  3059. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  3060. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  3061. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  3062. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  3063. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  3064. *
  3065. * @package ezUser
  3066. * @author Dominic Sayers <dominic@sayers.cc>
  3067. * @copyright 2008-2010 Dominic Sayers
  3068. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  3069. * @link http://code.google.com/p/ezuser/
  3070. * @version 0.25.5 - Floating div dialog boxes
  3071. */
  3072. /*jslint eqeqeq: true, immed: true, nomen: true, onevar: true, regexp: true, undef: true */
  3073. /*global window, document, event, ActiveXObject */ // For JSLint
  3074. //"use strict";
  3075. /**
  3076. * All ezUser functionality is held in this class to avoid namespace clashes
  3077. */
  3078. function C_ezUser() {
  3079. if (!(this instanceof arguments.callee)) {throw Error('Constructor called as a function');}
  3080. // Generated code - do not modify in built package
  3081. /**
  3082. *
  3083. * Secure Hash Algorithm (SHA256)
  3084. * http://www.webtoolkit.info/
  3085. *
  3086. * Original code by Angel Marin, Paul Johnston.
  3087. *
  3088. **/
  3089. function SHA256(s){
  3090. var chrsz = 8, hexcase = 0;
  3091. function safe_add (x, y) {
  3092. var lsw = (x & 0xFFFF) + (y & 0xFFFF),
  3093. msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  3094. return (msw << 16) | (lsw & 0xFFFF);
  3095. }
  3096. function S (X, n) { return ( X >>> n ) | (X << (32 - n)); }
  3097. function R (X, n) { return ( X >>> n ); }
  3098. function Ch(x, y, z) { return ((x & y) ^ ((~x) & z)); }
  3099. function Maj(x, y, z) { return ((x & y) ^ (x & z) ^ (y & z)); }
  3100. function Sigma0256(x) { return (S(x, 2) ^ S(x, 13) ^ S(x, 22)); }
  3101. function Sigma1256(x) { return (S(x, 6) ^ S(x, 11) ^ S(x, 25)); }
  3102. function Gamma0256(x) { return (S(x, 7) ^ S(x, 18) ^ R(x, 3)); }
  3103. function Gamma1256(x) { return (S(x, 17) ^ S(x, 19) ^ R(x, 10)); }
  3104. function core_sha256 (m, l) {
  3105. var K = [0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0xFC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x6CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2],
  3106. HASH = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19],
  3107. W = new Array(64),
  3108. a, b, c, d, e, f, g, h, i, j,
  3109. T1, T2;
  3110. m[l >> 5] |= 0x80 << (24 - l % 32);
  3111. m[((l + 64 >> 9) << 4) + 15] = l;
  3112. for ( var i = 0; i<m.length; i+=16 ) {
  3113. a = HASH[0];
  3114. b = HASH[1];
  3115. c = HASH[2];
  3116. d = HASH[3];
  3117. e = HASH[4];
  3118. f = HASH[5];
  3119. g = HASH[6];
  3120. h = HASH[7];
  3121. for ( var j = 0; j<64; j++) {
  3122. if (j < 16) W[j] = m[j + i];
  3123. else W[j] = safe_add(safe_add(safe_add(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
  3124. T1 = safe_add(safe_add(safe_add(safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
  3125. T2 = safe_add(Sigma0256(a), Maj(a, b, c));
  3126. h = g;
  3127. g = f;
  3128. f = e;
  3129. e = safe_add(d, T1);
  3130. d = c;
  3131. c = b;
  3132. b = a;
  3133. a = safe_add(T1, T2);
  3134. }
  3135. HASH[0] = safe_add(a, HASH[0]);
  3136. HASH[1] = safe_add(b, HASH[1]);
  3137. HASH[2] = safe_add(c, HASH[2]);
  3138. HASH[3] = safe_add(d, HASH[3]);
  3139. HASH[4] = safe_add(e, HASH[4]);
  3140. HASH[5] = safe_add(f, HASH[5]);
  3141. HASH[6] = safe_add(g, HASH[6]);
  3142. HASH[7] = safe_add(h, HASH[7]);
  3143. }
  3144. return HASH;
  3145. }
  3146. function str2binb (str) {
  3147. var bin = Array();
  3148. var mask = (1 << chrsz) - 1;
  3149. for(var i = 0; i < str.length * chrsz; i += chrsz) {
  3150. bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
  3151. }
  3152. return bin;
  3153. }
  3154. function Utf8Encode(string) {
  3155. string = string.replace(/\\r\\n/g,"\\n");
  3156. var utftext = "";
  3157. for (var n = 0; n < string.length; n++) {
  3158. var c = string.charCodeAt(n);
  3159. if (c < 128) {
  3160. utftext += String.fromCharCode(c);
  3161. }
  3162. else if((c > 127) && (c < 2048)) {
  3163. utftext += String.fromCharCode((c >> 6) | 192);
  3164. utftext += String.fromCharCode((c & 63) | 128);
  3165. }
  3166. else {
  3167. utftext += String.fromCharCode((c >> 12) | 224);
  3168. utftext += String.fromCharCode(((c >> 6) & 63) | 128);
  3169. utftext += String.fromCharCode((c & 63) | 128);
  3170. }
  3171. }
  3172. return utftext;
  3173. }
  3174. function binb2hex (binarray) {
  3175. var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  3176. var str = "";
  3177. for(var i = 0; i < binarray.length * 4; i++) {
  3178. str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
  3179. hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
  3180. }
  3181. return str;
  3182. }
  3183. s = Utf8Encode(s);
  3184. return binb2hex(core_sha256(str2binb(s), s.length * chrsz));
  3185. }
  3186. // End of generated code
  3187. this.getControl = function (id) {return document.getElementById(id);};
  3188. this.getValue = function (id) {return this.getControl(id).value;};
  3189. this.setValue = function (id, value) {this.getControl(id).value = value;};
  3190. var that = this;
  3191. // ---------------------------------------------------------------------------
  3192. function fireEvent(control, eventType, detail) {
  3193. var e, result; // Returned result from dispatchEvent
  3194. switch (eventType.toLowerCase()) {
  3195. case 'keyup':
  3196. case 'keydown':
  3197. if (document.createEventObject) {
  3198. // IE
  3199. e = document.createEventObject();
  3200. e.keyCode = detail;
  3201. result = control.fireEvent('on' + eventType);
  3202. } else if (window.KeyEvent) {
  3203. // Firefox
  3204. e = document.createEvent('KeyEvents');
  3205. e.initKeyEvent(eventType, true, true, window, false, false, false, false, detail, 0);
  3206. result = control.dispatchEvent(e);
  3207. } else {
  3208. e = document.createEvent('UIEvents');
  3209. e.initUIEvent(eventType, true, true, window, 1);
  3210. e.keyCode = detail;
  3211. result = control.dispatchEvent(e);
  3212. }
  3213. break;
  3214. case 'focus':
  3215. case 'blur':
  3216. case 'change':
  3217. if (document.createEventObject) {
  3218. // IE
  3219. e = document.createEventObject();
  3220. result = control.fireEvent('on' + eventType);
  3221. } else {
  3222. e = document.createEvent('UIEvents');
  3223. e.initUIEvent(eventType, true, true, window, 1);
  3224. result = control.dispatchEvent(e);
  3225. }
  3226. break;
  3227. case 'click':
  3228. if (document.createEventObject) {
  3229. // IE
  3230. e = document.createEventObject();
  3231. result = control.fireEvent('on' + eventType);
  3232. } else {
  3233. e = document.createEvent('MouseEvents');
  3234. e.initMouseEvent(eventType, true, true, window, 1);
  3235. result = control.dispatchEvent(e);
  3236. }
  3237. break;
  3238. }
  3239. return result;
  3240. }
  3241. // ---------------------------------------------------------------------------
  3242. function setFocus(control) {
  3243. var doEvent;
  3244. if (control.disabled) {return;}
  3245. if (typeof document.activeElement.onBlur === 'function') {fireEvent(document.activeElement, 'blur');}
  3246. if (typeof document.activeElement.onblur === 'function') {fireEvent(document.activeElement, 'blur');}
  3247. if (typeof control.onFocus === 'function') {doEvent = fireEvent(control, 'focus');}
  3248. if (typeof control.onfocus === 'function') {doEvent = fireEvent(control, 'focus');}
  3249. if (doEvent !== false) {control.focus();}
  3250. control.select();
  3251. }
  3252. // ---------------------------------------------------------------------------
  3253. function setInitialFocus(id) {
  3254. // Set focus to the first text control
  3255. var textId = '', control = null;
  3256. switch (id) {
  3257. case 'ezuser':
  3258. textId = 'ezuser-$tagUsername';
  3259. break;
  3260. case '$accountForm':
  3261. textId = '$accountForm-$tagEmail';
  3262. break;
  3263. }
  3264. if (textId !== '') {control = that.getControl(textId);}
  3265. if (control === null || typeof control === 'undefined' || control.disabled === 'disabled') {return;}
  3266. setFocus(control);
  3267. }
  3268. // ---------------------------------------------------------------------------
  3269. function hideControl(id) {
  3270. var control = that.getControl(id),
  3271. className = control.className + ' ezuser-hidden';
  3272. control.className = className;
  3273. control.style.display = 'none'; // belt and braces
  3274. }
  3275. // ---------------------------------------------------------------------------
  3276. function showControl(id) {
  3277. var control = that.getControl(id),
  3278. classString = control.className;
  3279. classString = classString.replace(/ezuser-hidden/g, '');
  3280. classString = classString.replace(/ {2}/g, ' ');
  3281. control.className = classString;
  3282. control.style.display = ''; // belt and braces
  3283. }
  3284. // ---------------------------------------------------------------------------
  3285. // Public properties
  3286. this.passwordSaved = '';
  3287. this.passwordDefault_SignIn = false;
  3288. this.passwordDefault_Account = false;
  3289. this.usernameDefault_Account = false;
  3290. // Public methods
  3291. // ---------------------------------------------------------------------------
  3292. this.showMessage = function (message, fail, messageType, instance) {
  3293. if (arguments.length < 1) {message = '';}
  3294. if (arguments.length < 2) {fail = false;}
  3295. if (arguments.length < 3) {messageType = 'message';}
  3296. if (arguments.length < 4) {instance = 'ezuser';}
  3297. var id = instance + '-' + messageType,
  3298. div = this.getControl(id),
  3299. classString = 'ezuser-' + messageType + ' ezuser-$stringRight',
  3300. subClass = (fail) ? 'fail' : 'info',
  3301. p;
  3302. if (div === null) {return;} // No such control
  3303. if (div.hasChildNodes()) {div.removeChild(div.firstChild);}
  3304. if (message !== '') {
  3305. p = document.createElement('p');
  3306. p.className = 'ezuser-message-' + subClass;
  3307. p.innerHTML = message;
  3308. div.className = classString;
  3309. div.appendChild(p);
  3310. }
  3311. div = this.getControl('ezuser-$tagVerbose');
  3312. if (div !== null) {div.parentNode.removeChild(div);}
  3313. };
  3314. // ---------------------------------------------------------------------------
  3315. this.bodyAppend = function (html) {
  3316. document.getElementsByTagName('body')[0].innerHTML += html;
  3317. };
  3318. /**
  3319. * Responds to various UI events and controls the appearance of the form's
  3320. * buttons
  3321. */
  3322. this.setButtonState = function (control, eventID, setOn) {
  3323. // eventID 1 = mouseover/mouseout
  3324. // 2 = focus/blur
  3325. // 4 = selected/unselected
  3326. if (control === null) {return false;}
  3327. var baseClass = control.className,
  3328. stateClass = 'ezuser-buttonstate-',
  3329. pos = baseClass.indexOf(stateClass),
  3330. currentState = Number(control.state);
  3331. currentState = (setOn) ? currentState | eventID : currentState & ~eventID;
  3332. control.state = String(currentState);
  3333. baseClass = (pos === -1) ? baseClass + ' ' : baseClass.substring(0, pos);
  3334. control.className = baseClass + stateClass + String(currentState);
  3335. return true;
  3336. };
  3337. /**
  3338. * Cookies! Mmmm.
  3339. */
  3340. this.cookies = {
  3341. sessionId: '',
  3342. username: '',
  3343. passwordHash: '',
  3344. staySignedIn: false,
  3345. rememberMe: false,
  3346. persist: function (name, value, days) {
  3347. var date, expires;
  3348. if (typeof days !== 'undefined') {
  3349. date = new Date();
  3350. date.setTime(date.getTime() + (days * 1000 * 3600 * 24));
  3351. expires = '; expires=' + date.toGMTString();
  3352. } else {
  3353. expires = '';
  3354. }
  3355. document.cookie = name + '=' + value + expires + '; path=/';
  3356. },
  3357. acquire: function (name) {
  3358. var i, c, carray = document.cookie.split(';');
  3359. name = name + '=';
  3360. for (i = 0; i < carray.length; i += 1) {
  3361. c = carray[i];
  3362. while (c.charAt(0) === ' ') {c = c.substring(1, c.length);}
  3363. if (c.indexOf(name) === 0) {return c.substring(name.length, c.length);}
  3364. }
  3365. return '';
  3366. },
  3367. remove: function (name) {this.persist(name, '', -1);},
  3368. read: function () {
  3369. this.sessionId = this.acquire('$sessionName');
  3370. this.username = this.acquire('$cookieUsername');
  3371. this.passwordHash = this.acquire('$cookiePassword');
  3372. this.staySignedIn = this.acquire('$cookieStaySignedIn');
  3373. this.staySignedIn = (this.staySignedIn === '') ? false : true;
  3374. if (this.username === '') {
  3375. this.staySignedIn = false;
  3376. } else {
  3377. this.rememberMe = true;
  3378. }
  3379. if (this.passwordHash === '') {
  3380. that.passwordDefault_SignIn = false;
  3381. this.staySignedIn = false;
  3382. } else {
  3383. that.passwordDefault_SignIn = true;
  3384. this.rememberMe = true;
  3385. }
  3386. },
  3387. update: function () {
  3388. this.username = that.getValue('ezuser-$tagUsername');
  3389. // if (typeof ajaxUnit === 'function') {ajaxUnit('passwordDefault_SignIn = ' + that.passwordDefault_SignIn, true);} // Debug
  3390. // if (typeof ajaxUnit === 'function') {ajaxUnit('this.passwordHash = ' + this.passwordHash, true);} // Debug
  3391. if (!that.passwordDefault_SignIn || (this.passwordHash === '')) {
  3392. var password = that.getValue('ezuser-$tagPassword');
  3393. this.passwordHash = SHA256('$remoteAddress' + SHA256(password));
  3394. }
  3395. // if (typeof ajaxUnit === 'function') {ajaxUnit('\\$remoteAddress = $remoteAddress', true);} // Debug
  3396. // if (typeof ajaxUnit === 'function') {ajaxUnit('this.passwordHash = ' + this.passwordHash, true);} // Debug
  3397. if (this.rememberMe) {
  3398. // Remember username & password for 30 days
  3399. this.persist('$cookieUsername', this.username, 30);
  3400. this.persist('$cookiePassword', this.passwordHash, 30);
  3401. } else {
  3402. this.remove('$cookieUsername');
  3403. this.remove('$cookiePassword');
  3404. }
  3405. if (this.staySignedIn) {
  3406. // Stay signed in for 2 weeks
  3407. this.persist('$cookieStaySignedIn', true, 24);
  3408. } else {
  3409. this.remove('$cookieStaySignedIn');
  3410. }
  3411. },
  3412. showPreferences: function () {
  3413. that.setButtonState(that.getControl('ezuser-$tagRememberMe'), 4, this.rememberMe);
  3414. that.setButtonState(that.getControl('ezuser-$tagStaySignedIn'), 4, this.staySignedIn);
  3415. },
  3416. toggleRememberMe: function() {
  3417. this.rememberMe = !this.rememberMe;
  3418. this.staySignedIn = (this.rememberMe) ? this.staySignedIn : false;
  3419. this.showPreferences();
  3420. this.update();
  3421. },
  3422. toggleStaySignedIn: function() {
  3423. this.staySignedIn = !this.staySignedIn;
  3424. this.rememberMe = (this.staySignedIn) ? true : this.rememberMe;
  3425. this.showPreferences();
  3426. this.update();
  3427. }
  3428. };
  3429. /**
  3430. * AJAX handling
  3431. */
  3432. this.ajax = {
  3433. xhr: new window.XMLHttpRequest(),
  3434. handleServerResponse: function () {
  3435. var id, fail, message, cancelButton;
  3436. if ((this.readyState === 4) && (this.status === 200)) {
  3437. if (isNaN(this.responseText)) {
  3438. id = this.getResponseHeader('ezUser-container');
  3439. if (this.responseXML !== null) {
  3440. that.fillContainersXML(this.responseXML);
  3441. } else if (id === null) {
  3442. that.bodyAppend(this.responseText);
  3443. } else {
  3444. that.fillContainerText(id, this.responseText);
  3445. }
  3446. that.dialog(id).position();
  3447. } else {
  3448. fail = true;
  3449. message = 'Server error, please try later';
  3450. cancelButton = that.getControl('ezuser-$actionCancel');
  3451. that.showMessage(message, fail);
  3452. if (cancelButton !== null) {
  3453. cancelButton.id = 'ezuser-$actionSignIn';
  3454. cancelButton.value = 'Sign in';
  3455. cancelButton.setAttribute('data-ezuser-action', '$actionSignIn');
  3456. }
  3457. }
  3458. if (typeof ajaxUnit === 'function') {ajaxUnit(this);} // Automated unit testing
  3459. }
  3460. },
  3461. serverTalk: function (URL, requestType, requestData) {
  3462. this.xhr.open(requestType, URL);
  3463. this.xhr.onreadystatechange = this.handleServerResponse;
  3464. this.xhr.setRequestHeader('Accept', 'text/html,application/ezuser');
  3465. if (requestType === 'POST') {this.xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');}
  3466. this.xhr.send(requestData);
  3467. },
  3468. execute: function (thisAction) {
  3469. var action,
  3470. passwordHash,
  3471. equals = '=',
  3472. requestType = 'GET',
  3473. requestData = '',
  3474. URL = '$URL',
  3475. delimPos = thisAction.indexOf('$delimPlus'),
  3476. control,
  3477. textNew,
  3478. readyState;
  3479. thisAction = (delimPos === -1) ? thisAction : '$action=' + thisAction;
  3480. delimPos = thisAction.indexOf(equals);
  3481. action = (delimPos === -1) ? thisAction : thisAction.slice(0, delimPos);
  3482. switch (action) {
  3483. case '$actionSignIn':
  3484. control = that.getControl('ezuser-$actionSignIn');
  3485. control.id = 'ezuser-$actionCancel';
  3486. control.value = 'Cancel';
  3487. control.setAttribute('data-ezuser-action', '$actionCancel');
  3488. that.showMessage('Signing in - please wait');
  3489. that.cookies.update(); // Updates ezuser.passwordHash;
  3490. passwordHash = SHA256(that.cookies.sessionId + that.cookies.passwordHash);
  3491. // if (typeof ajaxUnit === 'function') {ajaxUnit('sessionId = ' + that.cookies.sessionId, true);} // Debug
  3492. // if (typeof ajaxUnit === 'function') {ajaxUnit('passwordHash = ' + passwordHash, true);} // Debug
  3493. requestData = '$action=' + action;
  3494. requestData += '&$cookieUsername=' + that.getValue('ezuser-$tagUsername');
  3495. requestData += '&$cookiePassword=' + passwordHash;
  3496. requestType = 'POST';
  3497. break;
  3498. case '$actionValidate':
  3499. textNew = that.getValue('$accountForm-$tagNewUser');
  3500. requestData = '$action=' + action;
  3501. requestData += '&$tagNewUser=' + textNew;
  3502. requestData += '&$tagWizard=' + encodeURIComponent(that.getValue('$accountForm-$tagWizard'));
  3503. requestData += '&$tagEmail=' + encodeURIComponent(that.getValue('$accountForm-$tagEmail'));
  3504. requestData += '&$tagFirstName=' + encodeURIComponent(that.getValue('$accountForm-$tagFirstName'));
  3505. requestData += '&$tagLastName=' + encodeURIComponent(that.getValue('$accountForm-$tagLastName'));
  3506. requestData += '&$cookieUsername=' + that.getValue('$accountForm-$tagUsername');
  3507. if (!that.passwordDefault_Account || (textNew === '$stringTrue')) {
  3508. passwordHash = SHA256(that.getValue('$accountForm-$tagPassword'));
  3509. requestData += '&$cookiePassword=' + passwordHash;
  3510. }
  3511. requestType = 'POST';
  3512. break;
  3513. case '$actionCancel':
  3514. readyState = this.xhr.readyState;
  3515. if ((readyState > 0) && (readyState < 4)) {
  3516. // Cancel ongoing sign-in
  3517. this.xhr.abort();
  3518. this.xhr = new window.XMLHttpRequest();
  3519. }
  3520. return;
  3521. case '$actionResend':
  3522. URL += '?' + thisAction + equals + encodeURIComponent(that.getValue('$accountForm-$tagEmail'));
  3523. break;
  3524. case '$actionResetPassword': // Fall-through ->
  3525. case '$actionResetRequest':
  3526. URL += '?' + thisAction + equals + that.getValue('ezuser-$tagUsername');
  3527. break;
  3528. case '$actionReset':
  3529. passwordHash = SHA256(that.getValue('ezuser-$actionReset-$tagPassword'));
  3530. URL = window.location.href + '&$cookiePassword=' + passwordHash;
  3531. break;
  3532. default:
  3533. URL += '?' + thisAction;
  3534. break;
  3535. }
  3536. this.serverTalk(URL, requestType, requestData);
  3537. }
  3538. };
  3539. /**
  3540. * Account wizard page handling
  3541. */
  3542. this.wizard = {
  3543. page: 1,
  3544. changePage: function (delta) {
  3545. var nextPageId, nextPage;
  3546. if (this.getValue('$accountForm-$tagWizard') === '$stringFalse') {return;} // Not in wizard mode
  3547. this.page = (arguments.length === 0) ? 1 : this.page + delta;
  3548. if (this.page < 1) {this.page = 1;}
  3549. // Previous page
  3550. if (this.page === 1) {
  3551. hideControl('$accountForm-back'); // Hide 'Back' button
  3552. } else {
  3553. showControl('$accountForm-back'); // Show 'Back' button
  3554. hideControl('$accountForm-fieldset-' + (this.page - 1)); // Hide previous page
  3555. }
  3556. // Current page
  3557. showControl('$accountForm-fieldset-' + this.page); // Show this page
  3558. // Next page
  3559. nextPageId = '$accountForm-fieldset-' + (this.page + 1);
  3560. nextPage = this.getControl(nextPageId);
  3561. if (nextPage === null) {
  3562. hideControl('$accountForm-next'); // Hide 'Next' button
  3563. } else {
  3564. showControl('$accountForm-next'); // Show 'Next' button
  3565. hideControl(nextPageId); // Hide next page
  3566. }
  3567. },
  3568. pageNext: function () {this.changePage(1);},
  3569. pageBack: function () {this.changePage(-1);},
  3570. initialize: function () {this.page = 1;}
  3571. };
  3572. /**
  3573. * Responds to clicks on the ezUser form
  3574. */
  3575. this.click = function (control) {
  3576. var id = control.id,
  3577. action = control.getAttribute('data-ezuser-action');
  3578. switch (id) {
  3579. case 'ezuser-$actionAccountForm':
  3580. $accountClick;
  3581. break;
  3582. case 'ezuser-$tagRememberMe':
  3583. this.cookies.toggleRememberMe();
  3584. break;
  3585. case 'ezuser-$tagStaySignedIn':
  3586. this.cookies.toggleStaySignedIn();
  3587. break;
  3588. case '$accountForm-next':
  3589. this.wizard.pageNext(); // Next wizard page
  3590. break;
  3591. case '$accountForm-back':
  3592. this.wizard.pageBack(); // Previous wizard page
  3593. break;
  3594. case 'ezuser-$tagVerbose':
  3595. this.ajax.execute('$actionResultForm=' + control.value);
  3596. break;
  3597. case 'ezuser-$actionResetPassword': // Fall-through ->
  3598. case 'ezuser-$actionReset-OK': // Fall-through ->
  3599. case '$accountForm-$actionValidate':
  3600. if (this.localValidation(control.form.id)) {this.ajax.execute(action);}
  3601. break;
  3602. default:
  3603. if (action === null) {break;}
  3604. this.ajax.execute(action);
  3605. break;
  3606. }
  3607. return false;
  3608. };
  3609. /**
  3610. * Responds to keypresses on the ezUser form
  3611. */
  3612. this.keyPress = function (e) {
  3613. if (!e) {e = window.event;}
  3614. var formId, id, target, status = true;
  3615. // Process Carriage Return and tidy up form
  3616. target = (e.target) ? e.target : e.srcElement;
  3617. formId = target.form.id;
  3618. id = target.id;
  3619. if (formId === '$accountForm-form' && id === '$accountForm-$tagUsername') {
  3620. // If we are messing with the username then forget creating a default
  3621. this.usernameDefault_Account = false;
  3622. if ('' === this.removeIllegalCharacters(String.fromCharCode(e.charCode))) {
  3623. status = false; // cancel the event (i.e. don't allow the character)
  3624. }
  3625. }
  3626. return status;
  3627. };
  3628. this.keyUp = function (e) {
  3629. if (!e) {e = window.event;}
  3630. var formId, id, control, target;
  3631. // Process Carriage Return and tidy up form
  3632. target = (e.target) ? e.target : e.srcElement;
  3633. formId = target.form.id;
  3634. id = target.id;
  3635. switch (formId) {
  3636. case 'ezuser-$actionSignIn-form':
  3637. if (id === 'ezuser-$tagPassword' && this.passwordDefault_SignIn) {
  3638. // Forget password from cookie
  3639. this.cookies.passwordHash = '';
  3640. this.passwordDefault_SignIn = false;
  3641. }
  3642. if (e.keyCode === 13) {
  3643. this.click(this.getControl('ezuser-$actionSignIn'));
  3644. } else {
  3645. this.showMessage(); // Hide message
  3646. }
  3647. break;
  3648. case '$accountForm-form':
  3649. switch (id) {
  3650. case '$accountForm-$tagFirstName':
  3651. case '$accountForm-$tagLastName':
  3652. if (this.getValue('$accountForm-$tagUsername') === '') {this.usernameDefault_Account = true;}
  3653. if (this.usernameDefault_Account) {this.normalizeUsername(this.getValue('$accountForm-$tagFirstName') + this.getValue('$accountForm-$tagLastName'));}
  3654. break;
  3655. case '$accountForm-$tagPassword':
  3656. this.passwordSaved = target.value;
  3657. this.passwordDefault_Account = false;
  3658. break;
  3659. case '$accountForm-$tagConfirm':
  3660. this.passwordDefault_Account = false;
  3661. break;
  3662. }
  3663. if (e.keyCode === 13) {
  3664. control = this.getControl('$accountForm-$actionValidate');
  3665. if (control === null) {control = this.getControl('$accountForm-$modeEdit');}
  3666. this.click(control);
  3667. } else {
  3668. this.showMessage('', false, '$messageTypeText', '$accountForm'); // Hide message
  3669. }
  3670. break;
  3671. case 'ezuser-$actionReset-form':
  3672. if (e.keyCode === 13) {
  3673. this.click(this.getControl('ezuser-$actionReset-OK'));
  3674. } else {
  3675. this.showMessage('', false, '$messageTypeText'); // Hide message
  3676. }
  3677. break;
  3678. }
  3679. return true;
  3680. };
  3681. /**
  3682. * Manages the ezUser forms as dialog boxes
  3683. */
  3684. this.dialog = function(id) {
  3685. this.control = document.getElementById(id);
  3686. this.windowRect = function() {
  3687. var width = 0,
  3688. height = 0;
  3689. if (typeof window.innerWidth === 'number') {
  3690. width = window.innerWidth;
  3691. height = window.innerHeight;
  3692. } else if (document.documentElement && document.documentElement.clientWidth) {
  3693. width = document.documentElement.clientWidth;
  3694. height = document.documentElement.clientHeight;
  3695. } else if (document.body && document.body.clientWidth) {
  3696. width = document.body.clientWidth;
  3697. height = document.body.clientHeight;
  3698. }
  3699. return {width: width, height: height};
  3700. };
  3701. this.rect = function() {
  3702. return {width: this.control.offsetWidth, height: this.control.offsetHeight};
  3703. };
  3704. this.position = function() {
  3705. var dialogRect = this.rect(),
  3706. windowRect = this.windowRect(),
  3707. goldenSectionCenter = 2 * windowRect.height / (3 + Math.sqrt(5)),
  3708. dialogCenter = dialogRect.height / 2,
  3709. top = (goldenSectionCenter > dialogCenter) ? (goldenSectionCenter - dialogCenter) + 'px' : 0,
  3710. left = (windowRect.width > dialogRect.width) ? ((windowRect.width / 2) - (dialogRect.width / 2)) + 'px' : 0;
  3711. this.control.style.position = 'absolute';
  3712. this.control.style.left = left;
  3713. this.control.style.top = top;
  3714. };
  3715. return this;
  3716. };
  3717. // ---------------------------------------------------------------------------
  3718. this.fillContainerText = function (id, html) {
  3719. var container = this.getControl(id),
  3720. containerList,
  3721. formList,
  3722. formId;
  3723. if (container === null || typeof container === 'undefined') {
  3724. containerList = document.getElementsByTagName(id);
  3725. if (containerList === null || typeof containerList === 'undefined' || containerList.length === 0) {
  3726. window.alert('Can\\'t find a container \\'' + id + '\\' for this content: ' + html.substring(0, 256));
  3727. return;
  3728. } else {
  3729. container = containerList[0];
  3730. }
  3731. }
  3732. if (container.className.length === 0) {container.className = id;} // IE6 uses container.class
  3733. container.innerHTML = html;
  3734. formList = container.getElementsByTagName('form');
  3735. formId = ((typeof formList === 'undefined') || (formList.length === 0)) ? '' : formList[0].getAttribute('id');
  3736. switch (formId) {
  3737. case 'ezuser-$actionSignIn-form':
  3738. this.cookies.showPreferences();
  3739. if (this.cookies.rememberMe) {
  3740. this.passwordDefault_SignIn = true;
  3741. this.setValue('ezuser-$tagUsername', this.cookies.username);
  3742. this.setValue('ezuser-$tagPassword', '$passwordMask');
  3743. }
  3744. break;
  3745. case '$accountForm-form':
  3746. this.wizard.initialize(); // Set wizard to page 1
  3747. this.usernameDefault_Account = (this.getValue('$accountForm-$tagUsername') === '');
  3748. this.passwordDefault_Account = (this.getValue('$accountForm-$tagNewUser') !== '$stringTrue');
  3749. if (this.getValue('$accountForm-$tagUseSavedPassword') === '$stringTrue') {
  3750. this.setValue('$accountForm-$tagPassword', this.passwordSaved);
  3751. this.setValue('$accountForm-$tagConfirm', this.passwordSaved);
  3752. } else {
  3753. this.savedPassword = '';
  3754. }
  3755. break;
  3756. }
  3757. setInitialFocus(id);
  3758. };
  3759. // ---------------------------------------------------------------------------
  3760. this.fillContainersXML = function (xml) {
  3761. var i, iHalt, id, html, formNode, formList;
  3762. formList = xml.childNodes;
  3763. iHalt = formList.length;
  3764. for (i = 0; i < iHalt; i++) {
  3765. formNode = formList[i];
  3766. switch (formNode.nodeType) {
  3767. case 1: // Node.ELEMENT_NODE: // recurse
  3768. this.fillContainersXML(formNode);
  3769. break;
  3770. case 4: // Node.CDATA_SECTION_NODE: // fill the container
  3771. id = formNode.parentNode.getAttribute('container');
  3772. html = formNode.nodeValue;
  3773. this.fillContainerText(id, html);
  3774. break;
  3775. case 3: // Node.TEXT_NODE:
  3776. case 7: // Node.PROCESSING_INSTRUCTION_NODE: // Usually caused by PHP passing an error message along with the XHR content
  3777. case 8: // Node.COMMENT_NODE
  3778. break; // Ignore
  3779. default:
  3780. window.alert('I wasn\\'t expecting a node type of ' + formNode.nodeType);
  3781. break;
  3782. }
  3783. }
  3784. };
  3785. // ---------------------------------------------------------------------------
  3786. this.localValidation = function (formId) {
  3787. var control,
  3788. textEmail,
  3789. textUsername,
  3790. textPassword,
  3791. textConfirm,
  3792. textNew,
  3793. instance,
  3794. message = '';
  3795. switch (formId) {
  3796. case '$accountForm-form':
  3797. textEmail = this.getControl('$accountForm-$tagEmail');
  3798. textUsername = this.getControl('$accountForm-$tagUsername');
  3799. textPassword = this.getControl('$accountForm-$tagPassword');
  3800. textConfirm = this.getControl('$accountForm-$tagConfirm');
  3801. textNew = this.getControl('$accountForm-$tagNewUser');
  3802. instance = '$accountForm';
  3803. // Valid email address
  3804. if (textEmail.value === '') {
  3805. message = 'You must provide an email address';
  3806. control = textEmail;
  3807. } else {
  3808. // Valid username
  3809. this.normalizeUsername(textUsername.value);
  3810. if (textUsername.value === '') {
  3811. message = 'The username cannot be blank';
  3812. control = textUsername;
  3813. } else {
  3814. // Password OK?
  3815. if (textPassword.value !== textConfirm.value) {
  3816. message = 'Passwords are not the same';
  3817. } else if (this.passwordDefault_Account) {
  3818. if (textNew.value === '$stringTrue') {message = 'Password cannot be blank';}
  3819. } else if (textPassword.value === '') {
  3820. message = 'Password cannot be blank';
  3821. }
  3822. control = textPassword;
  3823. }
  3824. }
  3825. break;
  3826. case 'ezuser-$actionReset-form':
  3827. textPassword = this.getControl('ezuser-$actionReset-$tagPassword');
  3828. textConfirm = this.getControl('ezuser-$actionReset-$tagConfirm');
  3829. instance = 'ezuser';
  3830. control = textPassword;
  3831. // Password OK?
  3832. if (textPassword.value !== textConfirm.value) {
  3833. message = 'Passwords are not the same';
  3834. } else if (textPassword.value === '') {
  3835. message = 'Password cannot be blank';
  3836. }
  3837. break;
  3838. case 'ezuser-$actionResetRequest-form':
  3839. textUsername = this.getControl('ezuser-$tagUsername');
  3840. instance = 'ezuser';
  3841. control = textUsername;
  3842. // Username entered?
  3843. if (textUsername.value === '') {message = 'Username cannot be blank';}
  3844. break;
  3845. }
  3846. if (message === '') {
  3847. return true;
  3848. } else {
  3849. this.showMessage(message, true, '$messageTypeText', instance);
  3850. setFocus(control);
  3851. return false;
  3852. }
  3853. };
  3854. // ---------------------------------------------------------------------------
  3855. this.removeIllegalCharacters = function (restrictedString) {
  3856. var regexString = '[^0-9A-Za-z_-]',
  3857. regex = new RegExp(regexString, 'g');
  3858. return restrictedString.replace(regex, '');
  3859. }
  3860. this.normalizeUsername = function (username) {
  3861. username = this.removeIllegalCharacters(username);
  3862. var control = this.getControl('$accountForm-$tagUsername');
  3863. control.defaultValue = username;
  3864. control.value = username;
  3865. };
  3866. // ---------------------------------------------------------------------------
  3867. this.addStyleSheet = function () {
  3868. var htmlHead = document.getElementsByTagName('head')[0],
  3869. nodeList = htmlHead.getElementsByTagName('link'),
  3870. elementCount = nodeList.length,
  3871. found = false,
  3872. i, node;
  3873. for (i = 0; i < elementCount; i++) {
  3874. if (nodeList[i].title === 'ezUser') {
  3875. found = true;
  3876. break;
  3877. }
  3878. }
  3879. if (!found) {
  3880. // Add style sheet
  3881. node = document.createElement('link');
  3882. node.type = 'text/css';
  3883. node.rel = 'stylesheet';
  3884. node.href = '$URL?$actionCSS';
  3885. node.title = 'ezUser';
  3886. htmlHead.appendChild(node);
  3887. }
  3888. };
  3889. this.passwordFocus = function (control) {
  3890. switch (control.form.id) {
  3891. case 'ezuser-$actionSignIn-form':
  3892. if (this.passwordDefault_SignIn) {control.value = '';}
  3893. break;
  3894. case '$accountForm-form':
  3895. if (this.passwordDefault_Account) {
  3896. this.setValue('$accountForm-$tagPassword', '');
  3897. this.setValue('$accountForm-$tagConfirm', '');
  3898. }
  3899. break;
  3900. }
  3901. return true;
  3902. };
  3903. this.passwordBlur = function (control) {
  3904. switch (control.form.id) {
  3905. case 'ezuser-$actionSignIn-form':
  3906. if (this.passwordDefault_SignIn) {control.value = '$passwordMask';}
  3907. break;
  3908. case '$accountForm-form':
  3909. if (this.passwordDefault_Account) {
  3910. this.setValue('$accountForm-$tagPassword', '$passwordMask');
  3911. this.setValue('$accountForm-$tagConfirm', '$passwordMask');
  3912. }
  3913. break;
  3914. }
  3915. return true;
  3916. };
  3917. /**
  3918. * Constructor
  3919. */
  3920. this.cookies.read();
  3921. this.addStyleSheet();
  3922. }
  3923. /**
  3924. * Do stuff
  3925. */
  3926. var ezUser = new C_ezUser();
  3927. $immediateJavascript
  3928. GENERATED;
  3929. // Generated code - do not modify in built package
  3930. if ($sendToBrowser) {self::sendContent($js, '', 'text/javascript'); return '';} else return $js;
  3931. }
  3932. public static /*.string.*/ function inlineBitmap(/*.string.*/ $id, $sendToBrowser = true) {
  3933. switch ($id) {
  3934. case 'close':
  3935. $mimeType = 'image/gif';
  3936. $bitmap = base64_decode('R0lGODlhKAAoAIQaAMzMzM3Nzc7Ozs/Pz9DQ0NLS0tXV1djY2Nra2tvb293d3d7e3uDg4OHh4ePj4+np6erq6uzs7O7u7vPz8/X19fb29vn5+fr6+vz8/P7+/v///////////////////////yH+IUNvcHlyaWdodCAoYykgMjAxMCBEb21pbmljIFNheWVycwAh+QQBCgAfACwAAAAAKAAoAAAF/uAnjmT5EcQQBANqvvArtI00WVlmTVKTCrHgaOagaI7I5JHiGACFMsYlSZE8HI6HxIi8MJ5QkaBQQU4aBYB6rS40JshKARwcHDDHCoLN5yPKGhgHA0IDCxkaGQ59jHwOiBkLhDACB4gYe42aagh4GQd0JAV4GAabpwAGpAUyZRmZqJsIiBWhAgxHi2sKEQmoCxEMbA5HXyQDUxVsuEcQmxBICmxlF5MfAsQasAARSc6M0EgRbAi5TwNGE3wLSt9s4Ui+bHAUkwRHDX3wze/tfQ1HCIgAqCGNPn8A9mlwx6YAvhMSNFDQpBBCRU1GJKCAI+GZEoSNIk5IYUHDg1MK5pEwZPRAgwUViHR5BKmJWIYVMVGlXMnIJouSJ2d+5Mmn5UsCHIV+XLhJJIqIExtVvNgoo0CCBvlQpcrHoYYGIu59PeitX1k+BAV+QKdBHZsENNUoXMCH3iRsR7Z1Uyk1ybg15TQ4AINMg7JdfCkiEbaGmrVrzGQCYBCB7qkEEaStyWaMhABX22IxmmU4lIhRgUyJZqQqECsZlgKFXt0pEagYhiBJFv0okaRCd/LMbvTniKDHMcYAaoumkRs4eeaEGSKFihUsWrgc8WI6DBHtS5c06T79Gg0bOHTw8OGkfBgUKli4KB8CADs='); // Generated code - do not modify in built package
  3937. break;
  3938. default:
  3939. $mimeType = '';
  3940. $bitmap = 'Unknown bitmap';
  3941. }
  3942. if ($sendToBrowser) {self::sendContent($bitmap, 'bitmap', $mimeType); return '';} else return $bitmap;
  3943. }
  3944. // ---------------------------------------------------------------------------
  3945. // Account verification
  3946. // ---------------------------------------------------------------------------
  3947. private static /*.string.*/ function verify_renotify($username_or_email = '', $sendToBrowser = false) {
  3948. $success = self::verify_notify($username_or_email);
  3949. $message = ($success) ? 'Verification email has been resent.' : 'Verification email was not sent: please try again later';
  3950. $container = self::getInstanceId(self::ACTION_ACCOUNT . '-' . self::MESSAGE_TYPE_TEXT);
  3951. if ($sendToBrowser) {self::sendContent($message, $container); return '';} else return $message;
  3952. }
  3953. private static /*.void.*/ function verify(/*.string.*/ $verificationKey) {
  3954. $title = 'Email address verification';
  3955. $ezUser = self::lookup($verificationKey, self::TAGNAME_VERIFICATIONKEY);
  3956. switch ($ezUser->status()) {
  3957. case self::STATUS_PENDING:
  3958. self::verify_update($ezUser, $verificationKey);
  3959. $message = 'Your email address has been confirmed. You can now close this browser tab.';
  3960. break;
  3961. case self::STATUS_CONFIRMED:
  3962. $message = 'This email address has already been verified.';
  3963. break;
  3964. default:
  3965. $message = 'Sorry, this is not a valid account verification key.';
  3966. break;
  3967. }
  3968. self::htmlMessagePage($title, $message, true);
  3969. }
  3970. // ---------------------------------------------------------------------------
  3971. // Password reset
  3972. // ---------------------------------------------------------------------------
  3973. private static /*.void.*/ function passwordReset_validate(/*.string.*/ $username_or_email) {
  3974. if ($username_or_email === '')
  3975. self::htmlResetRequest($username_or_email, true);
  3976. else {
  3977. $message = (self::passwordReset_initialize($username_or_email))
  3978. ? 'An email has been sent to your registered address with instructions for resetting your password.'
  3979. : 'Couldn\'t complete password reset for this user.';
  3980. self::htmlMessageForm($message, self::ACTION_RESET, true);
  3981. }
  3982. }
  3983. private static /*.void.*/ function passwordReset_reset(/*.string.*/ $resetKey) {
  3984. $title = 'Password reset';
  3985. $ezUser = self::lookup($resetKey, self::TAGNAME_RESETKEY);
  3986. if ($ezUser->status() === self::STATUS_UNKNOWN) {
  3987. self::htmlMessagePage($title, 'Sorry, this is not a valid password reset key.', true);
  3988. return;
  3989. }
  3990. // Is there a password hash in $_GET?
  3991. if (array_key_exists(self::COOKIE_PASSWORD, $_GET)) {
  3992. // Attempt to update user's account with the new password
  3993. $passwordHash = (string) $_GET[self::COOKIE_PASSWORD];
  3994. self::passwordReset_update($ezUser, $passwordHash);
  3995. self::htmlMessagePage($title, 'Your password has been updated. You can now close this browser tab.', true);
  3996. } else {
  3997. // Get the new password from the user
  3998. self::htmlResetPassword($ezUser, true);
  3999. }
  4000. }
  4001. // ---------------------------------------------------------------------------
  4002. // Secure content handling
  4003. // ---------------------------------------------------------------------------
  4004. private static /*.string.*/ function htmlSecureContent($sendToBrowser = false) {
  4005. $ezUser = self::getSessionObject();
  4006. $refererKey = 'HTTP_REFERER';
  4007. if (array_key_exists($refererKey, $_SERVER)) {
  4008. $referer = $_SERVER[$refererKey];
  4009. if ($ezUser->authenticated()) {
  4010. $html = self::getSecureContent($referer);
  4011. } else {
  4012. header('HTTP/1.1 403 Forbidden', false, 403);
  4013. $referer = (string) str_replace('http://' . $_SERVER['HTTP_HOST'], '' , $referer);
  4014. $html = <<<HTML
  4015. <h1>Forbidden</h1>
  4016. <p>You don't have permission to access $referer on this server.</p>
  4017. HTML;
  4018. }
  4019. } else {
  4020. $html = 'No referer';
  4021. }
  4022. if ($sendToBrowser) {self::sendContent($html, self::ACTION_BODY); return '';} else return $html;
  4023. }
  4024. // ---------------------------------------------------------------------------
  4025. // Sign in and sign out
  4026. // ---------------------------------------------------------------------------
  4027. private static /*.void.*/ function fatalError(/*.int.*/ $result, $more = '') {
  4028. self::htmlResultForm($result, $more, true);
  4029. exit;
  4030. }
  4031. // ---------------------------------------------------------------------------
  4032. /*. forward public static void function doActions(array[string]string $actions); .*/
  4033. private static /*.void.*/ function signOut() {
  4034. $ezUser = self::getSessionObject();
  4035. if (!$ezUser->authenticated()) return; // Not signed in so nothing to do
  4036. // Sign out then check if a post-signout function has been registered
  4037. $ezUser->authenticate(); // Sign out
  4038. $ezUser->addSignOutAction(self::ACTION_MAIN);
  4039. $signOutActions = $ezUser->signOutActions();
  4040. self::setSessionObject(new ezUser_base(), self::ACTION_ACCOUNT);
  4041. self::doActions(array(self::ACTION => $signOutActions));
  4042. }
  4043. // ---------------------------------------------------------------------------
  4044. // General action handling
  4045. // ---------------------------------------------------------------------------
  4046. private static /*.string.*/ function doAction($action = '', $id = '', $sendToBrowser = true) {
  4047. $html = '';
  4048. switch ($action) {
  4049. case self::ACTION_CONTAINER: $html = self::htmlContainer ($id, $sendToBrowser); break;
  4050. case self::ACTION_MAIN: $html = self::htmlControlPanel ($id, $sendToBrowser); break;
  4051. case self::ACTION_BITMAP: $html = self::inlineBitmap ($id, $sendToBrowser); break;
  4052. case self::ACTION_RESETREQUEST: $html = self::htmlResetRequest ($id, $sendToBrowser); break;
  4053. case self::ACTION_ACCOUNT: $html = self::htmlAccountForm ($id, false, false, $sendToBrowser); break;
  4054. case self::ACTION_ACCOUNTWIZARD: $html = self::htmlAccountForm ($id, false, true, $sendToBrowser); break;
  4055. case self::ACTION_STATUSTEXT: $html = self::statusText ((int) $id, '', $sendToBrowser); break;
  4056. case self::ACTION_RESULTTEXT: $html = self::resultText ((int) $id, '', $sendToBrowser); break;
  4057. case self::ACTION_RESULTFORM: $html = self::htmlResultForm ((int) $id, '', $sendToBrowser); break;
  4058. case self::ACTION_RESEND: $html = self::verify_renotify ($id, $sendToBrowser); break;
  4059. case self::ACTION_JAVASCRIPT: $html = self::htmlJavascript ($id, $sendToBrowser); break;
  4060. case self::ACTION_STYLESHEET: $html = self::htmlStyleSheet ($sendToBrowser); break;
  4061. case self::ACTION_BODY: $html = self::htmlSecureContent ($sendToBrowser); break;
  4062. case self::ACTION_ABOUT: $html = self::htmlAbout ($sendToBrowser); break;
  4063. case self::ACTION_ABOUTTEXT: $html = self::htmlAboutText ($sendToBrowser); break;
  4064. case self::ACTION_SOURCECODE: $html = self::htmlSourceCode ($sendToBrowser); break;
  4065. case self::ACTION_VERIFY: self::verify ($id); break;
  4066. case self::ACTION_RESETPASSWORD: self::passwordReset_validate ($id); break;
  4067. case self::ACTION_RESET: self::passwordReset_reset ($id); break;
  4068. case self::ACTION_SIGNOUT: self::signOut (); break;
  4069. default: self::fatalError (self::RESULT_UNKNOWNACTION, $action); break;
  4070. }
  4071. return $html;
  4072. }
  4073. // ---------------------------------------------------------------------------
  4074. /**
  4075. * Performs one or more actions
  4076. *
  4077. * To perform more than one action, specify them in the condensed format <action>[=<id>]
  4078. *
  4079. * <pre>
  4080. * ezuser.php?foo1+foo2+foo3=bar1+bar2+bar3
  4081. * </pre>
  4082. *
  4083. * or the extended format action=<action>[&id-<id>]
  4084. *
  4085. * <pre>
  4086. * ezuser.php?action=foo1+foo2+foo3&id=bar1+bar2+bar3
  4087. * </pre>
  4088. *
  4089. * By the time it reaches this function they will be in an array with the '+'
  4090. * delimiters replaced with spaces by the magic of PHP. So if you're calling
  4091. * this function with your own parameters you would specify them like this:
  4092. *
  4093. * <pre>
  4094. * [$actions] => Array
  4095. * (
  4096. * [action] => foo1 foo2 foo3
  4097. * [id] => bar1 bar2 bar3
  4098. * )
  4099. * </pre>
  4100. *
  4101. * Each space-delimited element in the <kbd>action</kbd> member of the {@link $actions}
  4102. * array will be performed.
  4103. *
  4104. * @param array $actions Same format as {@link http://www.php.net/$_GET $_GET} (which is where it usually comes from)
  4105. */
  4106. public static /*.void.*/ function doActions(/*.array[string]string.*/ $actions) {
  4107. // Translate from short form (ezuser.php?foo=bar) to extended form (ezuser.php?action=foo&id=bar)
  4108. if (!array_key_exists(self::ACTION, $actions)) {
  4109. $actions[self::TAGNAME_ID] = (string) reset($actions);
  4110. $actions[self::ACTION] = (string) key($actions);
  4111. }
  4112. $actionList = (array_key_exists(self::ACTION, $actions)) ? $actions[self::ACTION] : '';
  4113. $id = (array_key_exists(self::TAGNAME_ID, $actions)) ? $actions[self::TAGNAME_ID] : '';
  4114. if (strpos($actionList, self::DELIMITER_SPACE) !== false) {
  4115. $actionItems = explode(self::DELIMITER_SPACE, $actionList);
  4116. $content = /*.(array[string]string).*/ array();
  4117. foreach ($actionItems as $action) $content[self::getInstanceId($action)] = self::doAction($action, $id, false);
  4118. self::sendXML($content);
  4119. } else {
  4120. self::doAction($actionList, $id);
  4121. }
  4122. }
  4123. // ---------------------------------------------------------------------------
  4124. // 'Get' actions
  4125. // ---------------------------------------------------------------------------
  4126. // Methods may be commented out to reduce the attack surface if they are not
  4127. // required. Uncomment them if you need them.
  4128. // public static /*.void.*/ function getStatusText (/*.int.*/ $status, $more = '') {self::statusText($status, $more, true);}
  4129. // public static /*.void.*/ function getResultText (/*.int.*/ $result, $more = '') {self::resultText($result, $more, true);}
  4130. // public static /*.void.*/ function getStatusDescription (/*.int.*/ $status, $more = '') {self::statusDescription($status, $more, true);}
  4131. // public static /*.void.*/ function getResultDescription (/*.int.*/ $result, $more = '') {self::resultDescription($result, $more, true);}
  4132. public static /*.void.*/ function getResultForm (/*.int.*/ $result, $more = '') {self::htmlResultForm($result, $more, true);}
  4133. public static /*.void.*/ function getAccountForm ($mode = '', $newUser = false, $wizard = false) {self::htmlAccountForm($mode, $newUser, $wizard, true);}
  4134. // public static /*.void.*/ function getDashboard () {self::htmlDashboard( true);}
  4135. // public static /*.void.*/ function getSignInForm () {self::htmlSignInForm( true);}
  4136. public static /*.void.*/ function getControlPanel ($username = '') {self::htmlControlPanel($username, true);}
  4137. // public static /*.void.*/ function getStyleSheet () {self::htmlStyleSheet( true);}
  4138. // public static /*.void.*/ function getJavascript ($containerList = '') {self::htmlJavascript($containerList, true);}
  4139. public static /*.void.*/ function getContainer ($action = self::ACTION_MAIN) {self::htmlContainer($action, true);}
  4140. public static /*.void.*/ function getAbout () {self::htmlAbout( true);}
  4141. public static /*.void.*/ function getAboutText () {self::htmlAboutText( true);}
  4142. // public static /*.void.*/ function getSourceCode () {self::htmlSourceCode( true);}
  4143. }
  4144. // End of class ezUser
  4145. // Is this script included in another page or is it the HTTP target itself?
  4146. if (basename($_SERVER['SCRIPT_NAME']) === basename(__FILE__)) {
  4147. // This script has been called directly by the browser, so check what it has sent
  4148. if (is_array($_POST) && array_key_exists(ezUser::ACTION, $_POST)) {
  4149. switch ((string) $_POST[ezUser::ACTION]) {
  4150. case ezUser::ACTION_SIGNIN:
  4151. ezUser::signIn($_POST);
  4152. ezUser::getControlPanel();
  4153. break;
  4154. case ezUser::ACTION_VALIDATE:
  4155. ezUser::save($_POST);
  4156. ezUser::getAccountForm(ezUser::ACCOUNT_MODE_RESULT, ($_POST[ezUser::TAGNAME_NEWUSER] === ezUser::STRING_TRUE), ($_POST[ezUser::TAGNAME_WIZARD] === ezUser::STRING_TRUE));
  4157. break;
  4158. default:
  4159. ezUser::getResultForm(ezUser::RESULT_UNKNOWNACTION);
  4160. break;
  4161. }
  4162. } else if (is_array($_GET) && (count($_GET) > 0)) {
  4163. ezUser::doActions(/*.(array[string]string).*/ $_GET);
  4164. } else {
  4165. ezUser::getAbout(); // Nothing useful in $_GET or $_POST, so give a friendly greeting
  4166. }
  4167. }
  4168. ?>