Cimy User Extra Fields - Version 2.5.1

Version Description

Download this release

Release Info

Developer Cimmo
Plugin Icon wp plugin Cimy User Extra Fields
Version 2.5.1
Comparing to
See all releases

Code changes from version 2.5.0 to 2.5.1

Files changed (109) hide show
  1. README_OFFICIAL.txt +5 -0
  2. cimy_uef_register.php +3 -3
  3. cimy_user_extra_fields.php +5 -4
  4. css/cimy_uef_register.css +5 -1
  5. readme.txt +5 -1
  6. securimage/README.txt +76 -3
  7. securimage/WavFile.php +1864 -0
  8. securimage/audio/0.wav +0 -0
  9. securimage/audio/1.wav +0 -0
  10. securimage/audio/2.wav +0 -0
  11. securimage/audio/3.wav +0 -0
  12. securimage/audio/4.wav +0 -0
  13. securimage/audio/5.wav +0 -0
  14. securimage/audio/6.wav +0 -0
  15. securimage/audio/7.wav +0 -0
  16. securimage/audio/8.wav +0 -0
  17. securimage/audio/9.wav +0 -0
  18. securimage/audio/A.wav +0 -0
  19. securimage/audio/B.wav +0 -0
  20. securimage/audio/C.wav +0 -0
  21. securimage/audio/D.wav +0 -0
  22. securimage/audio/E.wav +0 -0
  23. securimage/audio/F.wav +0 -0
  24. securimage/audio/G.wav +0 -0
  25. securimage/audio/H.wav +0 -0
  26. securimage/audio/I.wav +0 -0
  27. securimage/audio/J.wav +0 -0
  28. securimage/audio/K.wav +0 -0
  29. securimage/audio/L.wav +0 -0
  30. securimage/audio/M.wav +0 -0
  31. securimage/audio/N.wav +0 -0
  32. securimage/audio/O.wav +0 -0
  33. securimage/audio/P.wav +0 -0
  34. securimage/audio/Q.wav +0 -0
  35. securimage/audio/R.wav +0 -0
  36. securimage/audio/S.wav +0 -0
  37. securimage/audio/T.wav +0 -0
  38. securimage/audio/U.wav +0 -0
  39. securimage/audio/V.wav +0 -0
  40. securimage/audio/W.wav +0 -0
  41. securimage/audio/X.wav +0 -0
  42. securimage/audio/Y.wav +0 -0
  43. securimage/audio/Z.wav +0 -0
  44. securimage/audio/en/0.wav +0 -0
  45. securimage/audio/en/1.wav +0 -0
  46. securimage/audio/en/10.wav +0 -0
  47. securimage/audio/en/11.wav +0 -0
  48. securimage/audio/en/12.wav +0 -0
  49. securimage/audio/en/13.wav +0 -0
  50. securimage/audio/en/14.wav +0 -0
  51. securimage/audio/en/15.wav +0 -0
  52. securimage/audio/en/16.wav +0 -0
  53. securimage/audio/en/17.wav +0 -0
  54. securimage/audio/en/18.wav +0 -0
  55. securimage/audio/en/19.wav +0 -0
  56. securimage/audio/en/2.wav +0 -0
  57. securimage/audio/en/20.wav +0 -0
  58. securimage/audio/en/3.wav +0 -0
  59. securimage/audio/en/4.wav +0 -0
  60. securimage/audio/en/5.wav +0 -0
  61. securimage/audio/en/6.wav +0 -0
  62. securimage/audio/en/7.wav +0 -0
  63. securimage/audio/en/8.wav +0 -0
  64. securimage/audio/en/9.wav +0 -0
  65. securimage/audio/en/A.wav +0 -0
  66. securimage/audio/en/B.wav +0 -0
  67. securimage/audio/en/C.wav +0 -0
  68. securimage/audio/en/D.wav +0 -0
  69. securimage/audio/en/E.wav +0 -0
  70. securimage/audio/en/F.wav +0 -0
  71. securimage/audio/en/G.wav +0 -0
  72. securimage/audio/en/H.wav +0 -0
  73. securimage/audio/en/I.wav +0 -0
  74. securimage/audio/en/J.wav +0 -0
  75. securimage/audio/en/K.wav +0 -0
  76. securimage/audio/en/L.wav +0 -0
  77. securimage/audio/en/M.wav +0 -0
  78. securimage/audio/en/MINUS.wav +0 -0
  79. securimage/audio/en/N.wav +0 -0
  80. securimage/audio/en/O.wav +0 -0
  81. securimage/audio/en/P.wav +0 -0
  82. securimage/audio/en/PLUS.wav +0 -0
  83. securimage/audio/en/Q.wav +0 -0
  84. securimage/audio/en/R.wav +0 -0
  85. securimage/audio/en/S.wav +0 -0
  86. securimage/audio/en/T.wav +0 -0
  87. securimage/audio/en/TIMES.wav +0 -0
  88. securimage/audio/en/U.wav +0 -0
  89. securimage/audio/en/V.wav +0 -0
  90. securimage/audio/en/W.wav +0 -0
  91. securimage/audio/en/X.wav +0 -0
  92. securimage/audio/en/Y.wav +0 -0
  93. securimage/audio/en/Z.wav +0 -0
  94. securimage/audio/{error.wav → en/error.wav} +0 -0
  95. securimage/captcha.html +3 -3
  96. securimage/database/securimage.sq3 +0 -0
  97. securimage/database/securimage.sqlite +0 -0
  98. securimage/example_form.ajax.php +17 -16
  99. securimage/example_form.php +17 -16
  100. securimage/images/audio_icon.gif +0 -0
  101. securimage/images/audio_icon.png +0 -0
  102. securimage/images/refresh.gif +0 -0
  103. securimage/images/refresh.png +0 -0
  104. securimage/securimage.php +1188 -416
  105. securimage/securimage_play.php +2 -2
  106. securimage/securimage_play.swf +0 -0
  107. securimage/securimage_show.php +10 -10
  108. securimage/securimage_show_example.php +0 -65
  109. securimage/securimage_show_example2.php +0 -63
README_OFFICIAL.txt CHANGED
@@ -627,6 +627,11 @@ A lot of times I cannot reproduce the problem and I need more details, so if you
627
 
628
 
629
  CHANGELOG:
 
 
 
 
 
630
  v2.5.0 - 18/03/2013
631
  - Added support for hiding the username under standard WP registration, email will substitute it (non-MS only) (thanks to Matt Hoffman)
632
  - Added support for WordPress hidden fields rules under profile update
627
 
628
 
629
  CHANGELOG:
630
+ v2.5.1 - 06/05/2013
631
+ - Updated Securimage Captcha to v3.5.0
632
+ - Fixed captcha check was performed on /wp-admin/user-new.php page even without a captcha showed (MS only) (thanks to KZeni)
633
+ - Fixed strength password hint description is showed inline with the password strength when reCAPTCHA is also showed (thanks to coopersita)
634
+
635
  v2.5.0 - 18/03/2013
636
  - Added support for hiding the username under standard WP registration, email will substitute it (non-MS only) (thanks to Matt Hoffman)
637
  - Added support for WordPress hidden fields rules under profile update
cimy_uef_register.php CHANGED
@@ -296,7 +296,7 @@ function cimy_registration_check_mu_wrapper($data) {
296
  $errors = $data['errors'];
297
 
298
  // no we don't want to check again at this stage
299
- if (($_REQUEST['stage'] == "validate-blog-signup") && !empty($_REQUEST['confirm_form_nonce']) && ($_REQUEST['confirm_form_nonce'] == wp_create_nonce('confirm_form', 'confirm_form_nonce')))
300
  return $data;
301
 
302
  $errors = cimy_registration_check($user_login, $user_email, $errors);
@@ -1218,10 +1218,10 @@ function cimy_registration_form($errors=null, $show_type=0) {
1218
  <img id="captcha" align="left" style="padding-right: 5px; border: 0" src="<?php echo $cuef_securimage_webpath; ?>/securimage_show_captcha.php" alt="CAPTCHA Image" />
1219
  <object type="application/x-shockwave-flash" data="<?php echo $cuef_securimage_webpath; ?>/securimage_play.swf?audio_file=<?php echo $cuef_securimage_webpath; ?>/securimage_play.php&#038;bgColor1=#fff&#038;bgColor2=#fff&#038;iconColor=#777&#038;borderWidth=1&#038;borderColor=#000" height="19" width="19"><param name="movie" value="<?php echo $cuef_securimage_webpath; ?>/securimage_play.swf?audio_file=<?php echo $cuef_securimage_webpath; ?>/securimage_play.php&#038;bgColor1=#fff&#038;bgColor2=#fff&#038;iconColor=#777&#038;borderWidth=1&#038;borderColor=#000" /></object>
1220
  <br /><br /><br />
1221
- <a align="right" <?php if (!empty($obj_tabindex)) echo "tabindex=\"".$tabindex."\""; $tabindex++; ?> style="border-style: none" href="#" onclick="document.getElementById('captcha').src = '<?php echo $cuef_securimage_webpath; ?>/securimage_show_captcha.php?' + Math.random(); return false"><img src="<?php echo $cuef_securimage_webpath; ?>/images/refresh.gif" alt="<?php _e("Change image", $cimy_uef_domain); ?>" border="0" onclick="this.blur()" align="bottom" /></a>
1222
  </div>
1223
  <div style="width: <?php echo $width; ?>px; float: left; height: 50px; vertical-align: bottom; padding: 5px;">
1224
- <?php _e("Insert the code:", $cimy_uef_domain); ?>&nbsp;<input type="text" name="securimage_response_field" size="10" maxlength="6" tabindex="<?php echo $tabindex; $tabindex++; ?>" />
1225
  </div>
1226
  <?php
1227
  }
296
  $errors = $data['errors'];
297
 
298
  // no we don't want to check again at this stage
299
+ if ((!empty($_REQUEST['stage']) && $_REQUEST['stage'] == "validate-blog-signup") && !empty($_REQUEST['confirm_form_nonce']) && ($_REQUEST['confirm_form_nonce'] == wp_create_nonce('confirm_form', 'confirm_form_nonce')))
300
  return $data;
301
 
302
  $errors = cimy_registration_check($user_login, $user_email, $errors);
1218
  <img id="captcha" align="left" style="padding-right: 5px; border: 0" src="<?php echo $cuef_securimage_webpath; ?>/securimage_show_captcha.php" alt="CAPTCHA Image" />
1219
  <object type="application/x-shockwave-flash" data="<?php echo $cuef_securimage_webpath; ?>/securimage_play.swf?audio_file=<?php echo $cuef_securimage_webpath; ?>/securimage_play.php&#038;bgColor1=#fff&#038;bgColor2=#fff&#038;iconColor=#777&#038;borderWidth=1&#038;borderColor=#000" height="19" width="19"><param name="movie" value="<?php echo $cuef_securimage_webpath; ?>/securimage_play.swf?audio_file=<?php echo $cuef_securimage_webpath; ?>/securimage_play.php&#038;bgColor1=#fff&#038;bgColor2=#fff&#038;iconColor=#777&#038;borderWidth=1&#038;borderColor=#000" /></object>
1220
  <br /><br /><br />
1221
+ <a align="right" <?php if (!empty($obj_tabindex)) echo "tabindex=\"".$tabindex."\""; $tabindex++; ?> style="border-style: none" href="#" onclick="document.getElementById('captcha').src = '<?php echo $cuef_securimage_webpath; ?>/securimage_show_captcha.php?' + Math.random(); return false"><img src="<?php echo $cuef_securimage_webpath; ?>/images/refresh.png" alt="<?php _e("Change image", $cimy_uef_domain); ?>" border="0" onclick="this.blur()" align="bottom" height="19" width="19" /></a>
1222
  </div>
1223
  <div style="width: <?php echo $width; ?>px; float: left; height: 50px; vertical-align: bottom; padding: 5px;">
1224
+ <?php _e("Insert the code:", $cimy_uef_domain); ?>&nbsp;<input type="text" name="securimage_response_field" size="12" maxlength="16" tabindex="<?php echo $tabindex; $tabindex++; ?>" />
1225
  </div>
1226
  <?php
1227
  }
cimy_user_extra_fields.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Cimy User Extra Fields
4
  Plugin URI: http://www.marcocimmino.net/cimy-wordpress-plugins/cimy-user-extra-fields/
5
  Description: Add some useful fields to registration and user's info
6
- Version: 2.5.0
7
  Author: Marco Cimmino
8
  Author URI: mailto:cimmino.marco@gmail.com
9
  License: GPL2
@@ -160,7 +160,7 @@ require_once($cuef_plugin_dir.'/cimy_uef_profile.php');
160
  add_action('admin_init', 'cimy_uef_admin_init');
161
 
162
  $cimy_uef_name = "Cimy User Extra Fields";
163
- $cimy_uef_version = "2.5.0";
164
  $cimy_uef_url = "http://www.marcocimmino.net/cimy-wordpress-plugins/cimy-user-extra-fields/";
165
  $cimy_project_url = "http://www.marcocimmino.net/cimy-wordpress-plugins/support-the-cimy-project-paypal/";
166
 
@@ -506,8 +506,9 @@ if (is_multisite()) {
506
  // add extra fields to registration form
507
  add_action('signup_extra_fields', 'cimy_registration_form', 1);
508
 
509
- // add checks for extra fields in the registration form
510
- add_filter('wpmu_validate_user_signup', 'cimy_registration_check_mu_wrapper');
 
511
 
512
  // add custom login/registration css
513
  add_action('signup_header', 'cimy_uef_register_css');
3
  Plugin Name: Cimy User Extra Fields
4
  Plugin URI: http://www.marcocimmino.net/cimy-wordpress-plugins/cimy-user-extra-fields/
5
  Description: Add some useful fields to registration and user's info
6
+ Version: 2.5.1
7
  Author: Marco Cimmino
8
  Author URI: mailto:cimmino.marco@gmail.com
9
  License: GPL2
160
  add_action('admin_init', 'cimy_uef_admin_init');
161
 
162
  $cimy_uef_name = "Cimy User Extra Fields";
163
+ $cimy_uef_version = "2.5.1";
164
  $cimy_uef_url = "http://www.marcocimmino.net/cimy-wordpress-plugins/cimy-user-extra-fields/";
165
  $cimy_project_url = "http://www.marcocimmino.net/cimy-wordpress-plugins/support-the-cimy-project-paypal/";
166
 
506
  // add extra fields to registration form
507
  add_action('signup_extra_fields', 'cimy_registration_form', 1);
508
 
509
+ // add checks for extra fields in the registration form only
510
+ if (!is_admin() && !is_network_admin())
511
+ add_filter('wpmu_validate_user_signup', 'cimy_registration_check_mu_wrapper');
512
 
513
  // add custom login/registration css
514
  add_action('signup_header', 'cimy_uef_register_css');
css/cimy_uef_register.css CHANGED
@@ -23,7 +23,7 @@
23
  display: inline;
24
  }
25
 
26
- #pass-strength-result{
27
  padding-top: 3px;
28
  padding-right: 5px;
29
  padding-bottom: 3px;
@@ -41,3 +41,7 @@
41
  display: block;
42
  width: 93%;
43
  }
 
 
 
 
23
  display: inline;
24
  }
25
 
26
+ #pass-strength-result {
27
  padding-top: 3px;
28
  padding-right: 5px;
29
  padding-bottom: 3px;
41
  display: block;
42
  width: 93%;
43
  }
44
+
45
+ .indicator-hint {
46
+ display: table;
47
+ }
readme.txt CHANGED
@@ -5,7 +5,7 @@ Website link: http://www.marcocimmino.net/cimy-wordpress-plugins/cimy-user-extra
5
  Tags: cimy, admin, registration, profile, extra fields, avatar, gravatar, recaptcha, captcha
6
  Requires at least: 3.1
7
  Tested up to: 3.5
8
- Stable tag: 2.5.0
9
 
10
  Add some useful fields to registration and user's info
11
 
@@ -138,3 +138,7 @@ There are two supported ways of using this plug-in under WordPress MultiSite:
138
  2. User's profile with extra fields
139
  3. Main options page
140
  4. Add a new field form
 
 
 
 
5
  Tags: cimy, admin, registration, profile, extra fields, avatar, gravatar, recaptcha, captcha
6
  Requires at least: 3.1
7
  Tested up to: 3.5
8
+ Stable tag: 2.5.1
9
 
10
  Add some useful fields to registration and user's info
11
 
138
  2. User's profile with extra fields
139
  3. Main options page
140
  4. Add a new field form
141
+
142
+ == Changelog ==
143
+
144
+ http://www.marcocimmino.net/cimy-wordpress-plugins/cimy-user-extra-fields/all-versions-and-changelog/
securimage/README.txt CHANGED
@@ -2,7 +2,7 @@ NAME:
2
 
3
  Securimage - A PHP class for creating captcha images and audio with many options.
4
 
5
- VERSION: 3.0
6
 
7
  AUTHOR:
8
 
@@ -22,6 +22,7 @@ REQUIREMENTS:
22
  PHP 5.2 or greater
23
  GD 2.0
24
  FreeType (Required, for TTF fonts)
 
25
 
26
  SYNOPSIS:
27
 
@@ -52,7 +53,7 @@ DESCRIPTION:
52
 
53
 
54
  COPYRIGHT:
55
- Copyright (c) 2011 Drew Phillips
56
  All rights reserved.
57
 
58
  Redistribution and use in source and binary forms, with or without modification,
@@ -77,7 +78,13 @@ COPYRIGHT:
77
  POSSIBILITY OF SUCH DAMAGE.
78
 
79
  -----------------------------------------------------------------------------
80
- Flash code created for Securimage by Mario Romero (animario@hotmail.com)
 
 
 
 
 
 
81
  Many thanks for releasing this to the project!
82
 
83
  ------------------------------------------------------------------------------
@@ -106,3 +113,69 @@ COPYRIGHT:
106
  www.yannlecoroller.com
107
  yann@lecoroller.com
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  Securimage - A PHP class for creating captcha images and audio with many options.
4
 
5
+ VERSION: 3.5
6
 
7
  AUTHOR:
8
 
22
  PHP 5.2 or greater
23
  GD 2.0
24
  FreeType (Required, for TTF fonts)
25
+ PDO (if using Sqlite, MySQL, or PostgreSQL)
26
 
27
  SYNOPSIS:
28
 
53
 
54
 
55
  COPYRIGHT:
56
+ Copyright (c) 2013 Drew Phillips
57
  All rights reserved.
58
 
59
  Redistribution and use in source and binary forms, with or without modification,
78
  POSSIBILITY OF SUCH DAMAGE.
79
 
80
  -----------------------------------------------------------------------------
81
+ The WavFile.php class used in Securimage by Drew Phillips and Paul Voegler is
82
+ used under the BSD License. See WavFile.php for details.
83
+ Many thanks to Paul Voegler (http://www.voegler.eu/) for contributing to
84
+ Securimage.
85
+
86
+ -----------------------------------------------------------------------------
87
+ Flash code created for Securimage by Age Bosma & Mario Romero (animario@hotmail.com)
88
  Many thanks for releasing this to the project!
89
 
90
  ------------------------------------------------------------------------------
113
  www.yannlecoroller.com
114
  yann@lecoroller.com
115
 
116
+ -------------------------------------------------------------------------------
117
+ Portions of securimage_play.swf use the PopForge flash library for playing audio
118
+
119
+ /**
120
+ * Copyright(C) 2007 Andre Michelle and Joa Ebert
121
+ *
122
+ * PopForge is an ActionScript3 code sandbox developed by Andre Michelle and Joa Ebert
123
+ * http://sandbox.popforge.de
124
+ *
125
+ * PopforgeAS3Audio is free software; you can redistribute it and/or modify
126
+ * it under the terms of the GNU General Public License as published by
127
+ * the Free Software Foundation; either version 3 of the License, or
128
+ * (at your option) any later version.
129
+ *
130
+ * PopforgeAS3Audio is distributed in the hope that it will be useful,
131
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
132
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
133
+ * GNU General Public License for more details.
134
+ *
135
+ * You should have received a copy of the GNU General Public License
136
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
137
+ */
138
+
139
+ -------------------------------------------------------------------------------
140
+ Some graphics used are from the Humility Icon Pack by WorLord
141
+
142
+ License: GNU/GPL (http://findicons.com/pack/1723/humility)
143
+ http://findicons.com/icon/192558/gnome_volume_control
144
+ http://findicons.com/icon/192562/gtk_refresh
145
+
146
+ -------------------------------------------------------------------------------
147
+ Background noise sound files are from SoundJay.com
148
+ http://www.soundjay.com/tos.html
149
+
150
+ All sound effects on this website are created by us and protected under
151
+ the copyright laws, international treaty provisions and other applicable
152
+ laws. By downloading sounds, music or any material from this site implies
153
+ that you have read and accepted these terms and conditions:
154
+
155
+ Sound Effects
156
+ You are allowed to use the sounds free of charge and royalty free in your
157
+ projects (such as films, videos, games, presentations, animations, stage
158
+ plays, radio plays, audio books, apps) be it for commercial or
159
+ non-commercial purposes.
160
+
161
+ But you are NOT allowed to
162
+ - post the sounds (as sound effects or ringtones) on any website for
163
+ others to download, copy or use
164
+ - use them as a raw material to create sound effects or ringtones that
165
+ you will sell, distribute or offer for downloading
166
+ - sell, re-sell, license or re-license the sounds (as individual sound
167
+ effects or as a sound effects library) to anyone else
168
+ - claim the sounds as yours
169
+ - link directly to individual sound files
170
+ - distribute the sounds in apps or computer programs that are clearly
171
+ sound related in nature (such as sound machine, sound effect
172
+ generator, ringtone maker, funny sounds app, sound therapy app, etc.)
173
+ or in apps or computer programs that use the sounds as the program's
174
+ sound resource library for other people's use (such as animation
175
+ creator, digital book creator, song maker software, etc.). If you are
176
+ developing such computer programs, contact us for licensing options.
177
+
178
+ If you use the sound effects, please consider giving us a credit and
179
+ linking back to us but it's not required.
180
+
181
+
securimage/WavFile.php ADDED
@@ -0,0 +1,1864 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // error_reporting(E_ALL); ini_set('display_errors', 1); // uncomment this line for debugging
4
+
5
+ /**
6
+ * Project: PHPWavUtils: Classes for creating, reading, and manipulating WAV files in PHP<br />
7
+ * File: WavFile.php<br />
8
+ *
9
+ * Copyright (c) 2012, Drew Phillips
10
+ * All rights reserved.
11
+ *
12
+ * Redistribution and use in source and binary forms, with or without modification,
13
+ * are permitted provided that the following conditions are met:
14
+ *
15
+ * - Redistributions of source code must retain the above copyright notice,
16
+ * this list of conditions and the following disclaimer.
17
+ * - Redistributions in binary form must reproduce the above copyright notice,
18
+ * this list of conditions and the following disclaimer in the documentation
19
+ * and/or other materials provided with the distribution.
20
+ *
21
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
+ * POSSIBILITY OF SUCH DAMAGE.
32
+ *
33
+ * Any modifications to the library should be indicated clearly in the source code
34
+ * to inform users that the changes are not a part of the original software.<br /><br />
35
+ *
36
+ * @copyright 2012 Drew Phillips
37
+ * @author Drew Phillips <drew@drew-phillips.com>
38
+ * @author Paul Voegler <http://www.voegler.eu/>
39
+ * @version 1.0 (October 2012)
40
+ * @package PHPWavUtils
41
+ * @license BSD License
42
+ *
43
+ * Changelog:
44
+ *
45
+ * 1.0 (10/2/2012)
46
+ * - Fix insertSilence() creating invalid block size
47
+ *
48
+ * 1.0 RC1 (4/20/2012)
49
+ * - Initial release candidate
50
+ * - Supports 8, 16, 24, 32 bit PCM, 32-bit IEEE FLOAT, Extensible Format
51
+ * - Support for 18 channels of audio
52
+ * - Ability to read an offset from a file to reduce memory footprint with large files
53
+ * - Single-pass audio filter processing
54
+ * - Highly accurate and efficient mix and normalization filters (http://www.voegler.eu/pub/audio/)
55
+ * - Utility filters for degrading audio, and inserting silence
56
+ *
57
+ * 0.6 (4/12/2012)
58
+ * - Support 8, 16, 24, 32 bit and PCM float (Paul Voegler)
59
+ * - Add normalize filter, misc improvements and fixes (Paul Voegler)
60
+ * - Normalize parameters to filter() to use filter constants as array indices
61
+ * - Add option to mix filter to loop the target file if the source is longer
62
+ *
63
+ * 0.5 (4/3/2012)
64
+ * - Fix binary pack routine (Paul Voegler)
65
+ * - Add improved mixing function (Paul Voegler)
66
+ *
67
+ */
68
+
69
+ class WavFile
70
+ {
71
+ /*%******************************************************************************************%*/
72
+ // Class constants
73
+
74
+ /** @var int Filter flag for mixing two files */
75
+ const FILTER_MIX = 0x01;
76
+
77
+ /** @var int Filter flag for normalizing audio data */
78
+ const FILTER_NORMALIZE = 0x02;
79
+
80
+ /** @var int Filter flag for degrading audio data */
81
+ const FILTER_DEGRADE = 0x04;
82
+
83
+ /** @var int Maximum number of channels */
84
+ const MAX_CHANNEL = 18;
85
+
86
+ /** @var int Maximum sample rate */
87
+ const MAX_SAMPLERATE = 192000;
88
+
89
+ /** Channel Locations for ChannelMask */
90
+ const SPEAKER_DEFAULT = 0x000000;
91
+ const SPEAKER_FRONT_LEFT = 0x000001;
92
+ const SPEAKER_FRONT_RIGHT = 0x000002;
93
+ const SPEAKER_FRONT_CENTER = 0x000004;
94
+ const SPEAKER_LOW_FREQUENCY = 0x000008;
95
+ const SPEAKER_BACK_LEFT = 0x000010;
96
+ const SPEAKER_BACK_RIGHT = 0x000020;
97
+ const SPEAKER_FRONT_LEFT_OF_CENTER = 0x000040;
98
+ const SPEAKER_FRONT_RIGHT_OF_CENTER = 0x000080;
99
+ const SPEAKER_BACK_CENTER = 0x000100;
100
+ const SPEAKER_SIDE_LEFT = 0x000200;
101
+ const SPEAKER_SIDE_RIGHT = 0x000400;
102
+ const SPEAKER_TOP_CENTER = 0x000800;
103
+ const SPEAKER_TOP_FRONT_LEFT = 0x001000;
104
+ const SPEAKER_TOP_FRONT_CENTER = 0x002000;
105
+ const SPEAKER_TOP_FRONT_RIGHT = 0x004000;
106
+ const SPEAKER_TOP_BACK_LEFT = 0x008000;
107
+ const SPEAKER_TOP_BACK_CENTER = 0x010000;
108
+ const SPEAKER_TOP_BACK_RIGHT = 0x020000;
109
+ const SPEAKER_ALL = 0x03FFFF;
110
+
111
+ /** @var int PCM Audio Format */
112
+ const WAVE_FORMAT_PCM = 0x0001;
113
+
114
+ /** @var int IEEE FLOAT Audio Format */
115
+ const WAVE_FORMAT_IEEE_FLOAT = 0x0003;
116
+
117
+ /** @var int EXTENSIBLE Audio Format - actual audio format defined by SubFormat */
118
+ const WAVE_FORMAT_EXTENSIBLE = 0xFFFE;
119
+
120
+ /** @var string PCM Audio Format SubType - LE hex representation of GUID {00000001-0000-0010-8000-00AA00389B71} */
121
+ const WAVE_SUBFORMAT_PCM = "0100000000001000800000aa00389b71";
122
+
123
+ /** @var string IEEE FLOAT Audio Format SubType - LE hex representation of GUID {00000003-0000-0010-8000-00AA00389B71} */
124
+ const WAVE_SUBFORMAT_IEEE_FLOAT = "0300000000001000800000aa00389b71";
125
+
126
+
127
+ /*%******************************************************************************************%*/
128
+ // Properties
129
+
130
+ /** @var array Log base modifier lookup table for a given threshold (in 0.05 steps) used by normalizeSample.
131
+ * Adjusts the slope (1st derivative) of the log function at the threshold to 1 for a smooth transition
132
+ * from linear to logarithmic amplitude output. */
133
+ protected static $LOOKUP_LOGBASE = array(
134
+ 2.513, 2.667, 2.841, 3.038, 3.262,
135
+ 3.520, 3.819, 4.171, 4.589, 5.093,
136
+ 5.711, 6.487, 7.483, 8.806, 10.634,
137
+ 13.302, 17.510, 24.970, 41.155, 96.088
138
+ );
139
+
140
+ /** @var int The actual physical file size */
141
+ protected $_actualSize;
142
+
143
+ /** @var int The size of the file in RIFF header */
144
+ protected $_chunkSize;
145
+
146
+ /** @var int The size of the "fmt " chunk */
147
+ protected $_fmtChunkSize;
148
+
149
+ /** @var int The size of the extended "fmt " data */
150
+ protected $_fmtExtendedSize;
151
+
152
+ /** @var int The size of the "fact" chunk */
153
+ protected $_factChunkSize;
154
+
155
+ /** @var int Size of the data chunk */
156
+ protected $_dataSize;
157
+
158
+ /** @var int Size of the data chunk in the opened wav file */
159
+ protected $_dataSize_fp;
160
+
161
+ /** @var int Does _dataSize really reflect strlen($_samples)? Case when a wav file is read with readData = false */
162
+ protected $_dataSize_valid;
163
+
164
+ /** @var int Starting offset of data chunk */
165
+ protected $_dataOffset;
166
+
167
+ /** @var int The audio format - WavFile::WAVE_FORMAT_* */
168
+ protected $_audioFormat;
169
+
170
+ /** @var int The audio subformat - WavFile::WAVE_SUBFORMAT_* */
171
+ protected $_audioSubFormat;
172
+
173
+ /** @var int Number of channels in the audio file */
174
+ protected $_numChannels;
175
+
176
+ /** @var int The channel mask */
177
+ protected $_channelMask;
178
+
179
+ /** @var int Samples per second */
180
+ protected $_sampleRate;
181
+
182
+ /** @var int Number of bits per sample */
183
+ protected $_bitsPerSample;
184
+
185
+ /** @var int Number of valid bits per sample */
186
+ protected $_validBitsPerSample;
187
+
188
+ /** @var int NumChannels * BitsPerSample/8 */
189
+ protected $_blockAlign;
190
+
191
+ /** @var int Number of sample blocks */
192
+ protected $_numBlocks;
193
+
194
+ /** @var int Bytes per second */
195
+ protected $_byteRate;
196
+
197
+ /** @var string Binary string of samples */
198
+ protected $_samples;
199
+
200
+ /** @var resource The file pointer used for reading wavs from file or memory */
201
+ protected $_fp;
202
+
203
+
204
+ /*%******************************************************************************************%*/
205
+ // Special methods
206
+
207
+ /**
208
+ * WavFile Constructor.
209
+ *
210
+ * <code>
211
+ * $wav1 = new WavFile(2, 44100, 16); // new wav with 2 channels, at 44100 samples/sec and 16 bits per sample
212
+ * $wav2 = new WavFile('./audio/sound.wav'); // open and read wav file
213
+ * </code>
214
+ *
215
+ * @param string|int $numChannelsOrFileName (Optional) If string, the filename of the wav file to open. The number of channels otherwise. Defaults to 1.
216
+ * @param int|bool $sampleRateOrReadData (Optional) If opening a file and boolean, decides whether to read the data chunk or not. Defaults to true. The sample rate in samples per second otherwise. 8000 = standard telephone, 16000 = wideband telephone, 32000 = FM radio and 44100 = CD quality. Defaults to 8000.
217
+ * @param int $bitsPerSample (Optional) The number of bits per sample. Has to be 8, 16 or 24 for PCM audio or 32 for IEEE FLOAT audio. 8 = telephone, 16 = CD and 24 or 32 = studio quality. Defaults to 8.
218
+ * @throws WavFormatException
219
+ * @throws WavFileException
220
+ */
221
+ public function __construct($numChannelsOrFileName = null, $sampleRateOrReadData = null, $bitsPerSample = null)
222
+ {
223
+ $this->_actualSize = 44;
224
+ $this->_chunkSize = 36;
225
+ $this->_fmtChunkSize = 16;
226
+ $this->_fmtExtendedSize = 0;
227
+ $this->_factChunkSize = 0;
228
+ $this->_dataSize = 0;
229
+ $this->_dataSize_fp = 0;
230
+ $this->_dataSize_valid = true;
231
+ $this->_dataOffset = 44;
232
+ $this->_audioFormat = self::WAVE_FORMAT_PCM;
233
+ $this->_audioSubFormat = null;
234
+ $this->_numChannels = 1;
235
+ $this->_channelMask = self::SPEAKER_DEFAULT;
236
+ $this->_sampleRate = 8000;
237
+ $this->_bitsPerSample = 8;
238
+ $this->_validBitsPerSample = 8;
239
+ $this->_blockAlign = 1;
240
+ $this->_numBlocks = 0;
241
+ $this->_byteRate = 8000;
242
+ $this->_samples = '';
243
+ $this->_fp = null;
244
+
245
+
246
+ if (is_string($numChannelsOrFileName)) {
247
+ $this->openWav($numChannelsOrFileName, is_bool($sampleRateOrReadData) ? $sampleRateOrReadData : true);
248
+
249
+ } else {
250
+ $this->setNumChannels(is_null($numChannelsOrFileName) ? 1 : $numChannelsOrFileName)
251
+ ->setSampleRate(is_null($sampleRateOrReadData) ? 8000 : $sampleRateOrReadData)
252
+ ->setBitsPerSample(is_null($bitsPerSample) ? 8 : $bitsPerSample);
253
+ }
254
+ }
255
+
256
+ public function __destruct() {
257
+ if (is_resource($this->_fp)) $this->closeWav();
258
+ }
259
+
260
+ public function __clone() {
261
+ $this->_fp = null;
262
+ }
263
+
264
+ /**
265
+ * Output the wav file headers and data.
266
+ *
267
+ * @return string The encoded file.
268
+ */
269
+ public function __toString()
270
+ {
271
+ return $this->makeHeader() .
272
+ $this->getDataSubchunk();
273
+ }
274
+
275
+
276
+ /*%******************************************************************************************%*/
277
+ // Static methods
278
+
279
+ /**
280
+ * Unpacks a single binary sample to numeric value.
281
+ *
282
+ * @param string $sampleBinary (Required) The sample to decode.
283
+ * @param int $bitDepth (Optional) The bits per sample to decode. If omitted, derives it from the length of $sampleBinary.
284
+ * @return int|float The numeric sample value. Float for 32-bit samples. Returns null for unsupported bit depths.
285
+ */
286
+ public static function unpackSample($sampleBinary, $bitDepth = null)
287
+ {
288
+ if ($bitDepth === null) {
289
+ $bitDepth = strlen($sampleBinary) * 8;
290
+ }
291
+
292
+ switch ($bitDepth) {
293
+ case 8:
294
+ // unsigned char
295
+ return ord($sampleBinary);
296
+
297
+ case 16:
298
+ // signed short, little endian
299
+ $data = unpack('v', $sampleBinary);
300
+ $sample = $data[1];
301
+ if ($sample >= 0x8000) {
302
+ $sample -= 0x10000;
303
+ }
304
+ return $sample;
305
+
306
+ case 24:
307
+ // 3 byte packed signed integer, little endian
308
+ $data = unpack('C3', $sampleBinary);
309
+ $sample = $data[1] | ($data[2] << 8) | ($data[3] << 16);
310
+ if ($sample >= 0x800000) {
311
+ $sample -= 0x1000000;
312
+ }
313
+ return $sample;
314
+
315
+ case 32:
316
+ // 32-bit float
317
+ $data = unpack('f', $sampleBinary);
318
+ return $data[1];
319
+
320
+ default:
321
+ return null;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Packs a single numeric sample to binary.
327
+ *
328
+ * @param int|float $sample (Required) The sample to encode. Has to be within valid range for $bitDepth. Float values only for 32 bits.
329
+ * @param int $bitDepth (Required) The bits per sample to encode with.
330
+ * @return string The encoded binary sample. Returns null for unsupported bit depths.
331
+ */
332
+ public static function packSample($sample, $bitDepth)
333
+ {
334
+ switch ($bitDepth) {
335
+ case 8:
336
+ // unsigned char
337
+ return chr($sample);
338
+
339
+ case 16:
340
+ // signed short, little endian
341
+ if ($sample < 0) {
342
+ $sample += 0x10000;
343
+ }
344
+ return pack('v', $sample);
345
+
346
+ case 24:
347
+ // 3 byte packed signed integer, little endian
348
+ if ($sample < 0) {
349
+ $sample += 0x1000000;
350
+ }
351
+ return pack('C3', $sample & 0xff, ($sample >> 8) & 0xff, ($sample >> 16) & 0xff);
352
+
353
+ case 32:
354
+ // 32-bit float
355
+ return pack('f', $sample);
356
+
357
+ default:
358
+ return null;
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Unpacks a binary sample block to numeric values.
364
+ *
365
+ * @param string $sampleBlock (Required) The binary sample block (all channels).
366
+ * @param int $bitDepth (Required) The bits per sample to decode.
367
+ * @param int $numChannels (Optional) The number of channels to decode. If omitted, derives it from the length of $sampleBlock and $bitDepth.
368
+ * @return array The sample values as an array of integers of floats for 32 bits. First channel is array index 1.
369
+ */
370
+ public static function unpackSampleBlock($sampleBlock, $bitDepth, $numChannels = null) {
371
+ $sampleBytes = $bitDepth / 8;
372
+ if ($numChannels === null) {
373
+ $numChannels = strlen($sampleBlock) / $sampleBytes;
374
+ }
375
+
376
+ $samples = array();
377
+ for ($i = 0; $i < $numChannels; $i++) {
378
+ $sampleBinary = substr($sampleBlock, $i * $sampleBytes, $sampleBytes);
379
+ $samples[$i + 1] = self::unpackSample($sampleBinary, $bitDepth);
380
+ }
381
+
382
+ return $samples;
383
+ }
384
+
385
+ /**
386
+ * Packs an array of numeric channel samples to a binary sample block.
387
+ *
388
+ * @param array $samples (Required) The array of channel sample values. Expects float values for 32 bits and integer otherwise.
389
+ * @param int $bitDepth (Required) The bits per sample to encode with.
390
+ * @return string The encoded binary sample block.
391
+ */
392
+ public static function packSampleBlock($samples, $bitDepth) {
393
+ $sampleBlock = '';
394
+ foreach($samples as $sample) {
395
+ $sampleBlock .= self::packSample($sample, $bitDepth);
396
+ }
397
+
398
+ return $sampleBlock;
399
+ }
400
+
401
+ /**
402
+ * Normalizes a float audio sample. Maximum input range assumed for compression is [-2, 2].
403
+ * See http://www.voegler.eu/pub/audio/ for more information.
404
+ *
405
+ * @param float $sampleFloat (Required) The float sample to normalize.
406
+ * @param float $threshold (Required) The threshold or gain factor for normalizing the amplitude. <ul>
407
+ * <li> >= 1 - Normalize by multiplying by the threshold (boost - positive gain). <br />
408
+ * A value of 1 in effect means no normalization (and results in clipping). </li>
409
+ * <li> <= -1 - Normalize by dividing by the the absolute value of threshold (attenuate - negative gain). <br />
410
+ * A factor of 2 (-2) is about 6dB reduction in volume.</li>
411
+ * <li> [0, 1) - (open inverval - not including 1) - The threshold
412
+ * above which amplitudes are comressed logarithmically. <br />
413
+ * e.g. 0.6 to leave amplitudes up to 60% "as is" and compress above. </li>
414
+ * <li> (-1, 0) - (open inverval - not including -1 and 0) - The threshold
415
+ * above which amplitudes are comressed linearly. <br />
416
+ * e.g. -0.6 to leave amplitudes up to 60% "as is" and compress above. </li></ul>
417
+ * @return float The normalized sample.
418
+ **/
419
+ public static function normalizeSample($sampleFloat, $threshold) {
420
+ // apply positive gain
421
+ if ($threshold >= 1) {
422
+ return $sampleFloat * $threshold;
423
+ }
424
+
425
+ // apply negative gain
426
+ if ($threshold <= -1) {
427
+ return $sampleFloat / -$threshold;
428
+ }
429
+
430
+ $sign = $sampleFloat < 0 ? -1 : 1;
431
+ $sampleAbs = abs($sampleFloat);
432
+
433
+ // logarithmic compression
434
+ if ($threshold >= 0 && $threshold < 1 && $sampleAbs > $threshold) {
435
+ $loga = self::$LOOKUP_LOGBASE[(int)($threshold * 20)]; // log base modifier
436
+ return $sign * ($threshold + (1 - $threshold) * log(1 + $loga * ($sampleAbs - $threshold) / (2 - $threshold)) / log(1 + $loga));
437
+ }
438
+
439
+ // linear compression
440
+ $thresholdAbs = abs($threshold);
441
+ if ($threshold > -1 && $threshold < 0 && $sampleAbs > $thresholdAbs) {
442
+ return $sign * ($thresholdAbs + (1 - $thresholdAbs) / (2 - $thresholdAbs) * ($sampleAbs - $thresholdAbs));
443
+ }
444
+
445
+ // else ?
446
+ return $sampleFloat;
447
+ }
448
+
449
+
450
+ /*%******************************************************************************************%*/
451
+ // Getter and Setter methods for properties
452
+
453
+ public function getActualSize() {
454
+ return $this->_actualSize;
455
+ }
456
+
457
+ protected function setActualSize($actualSize = null) {
458
+ if (is_null($actualSize)) {
459
+ $this->_actualSize = 8 + $this->_chunkSize; // + "RIFF" header (ID + size)
460
+ } else {
461
+ $this->_actualSize = $actualSize;
462
+ }
463
+
464
+ return $this;
465
+ }
466
+
467
+ public function getChunkSize() {
468
+ return $this->_chunkSize;
469
+ }
470
+
471
+ protected function setChunkSize($chunkSize = null) {
472
+ if (is_null($chunkSize)) {
473
+ $this->_chunkSize = 4 + // "WAVE" chunk
474
+ 8 + $this->_fmtChunkSize + // "fmt " subchunk
475
+ ($this->_factChunkSize > 0 ? 8 + $this->_factChunkSize : 0) + // "fact" subchunk
476
+ 8 + $this->_dataSize + // "data" subchunk
477
+ ($this->_dataSize & 1); // padding byte
478
+ } else {
479
+ $this->_chunkSize = $chunkSize;
480
+ }
481
+
482
+ $this->setActualSize();
483
+
484
+ return $this;
485
+ }
486
+
487
+ public function getFmtChunkSize() {
488
+ return $this->_fmtChunkSize;
489
+ }
490
+
491
+ protected function setFmtChunkSize($fmtChunkSize = null) {
492
+ if (is_null($fmtChunkSize)) {
493
+ $this->_fmtChunkSize = 16 + $this->_fmtExtendedSize;
494
+ } else {
495
+ $this->_fmtChunkSize = $fmtChunkSize;
496
+ }
497
+
498
+ $this->setChunkSize() // implicit setActualSize()
499
+ ->setDataOffset();
500
+
501
+ return $this;
502
+ }
503
+
504
+ public function getFmtExtendedSize() {
505
+ return $this->_fmtExtendedSize;
506
+ }
507
+
508
+ protected function setFmtExtendedSize($fmtExtendedSize = null) {
509
+ if (is_null($fmtExtendedSize)) {
510
+ if ($this->_audioFormat == self::WAVE_FORMAT_EXTENSIBLE) {
511
+ $this->_fmtExtendedSize = 2 + 22; // extension size for WAVE_FORMAT_EXTENSIBLE
512
+ } elseif ($this->_audioFormat != self::WAVE_FORMAT_PCM) {
513
+ $this->_fmtExtendedSize = 2 + 0; // empty extension
514
+ } else {
515
+ $this->_fmtExtendedSize = 0; // no extension, only for WAVE_FORMAT_PCM
516
+ }
517
+ } else {
518
+ $this->_fmtExtendedSize = $fmtExtendedSize;
519
+ }
520
+
521
+ $this->setFmtChunkSize(); // implicit setSize(), setActualSize(), setDataOffset()
522
+
523
+ return $this;
524
+ }
525
+
526
+ public function getFactChunkSize() {
527
+ return $this->_factChunkSize;
528
+ }
529
+
530
+ protected function setFactChunkSize($factChunkSize = null) {
531
+ if (is_null($factChunkSize)) {
532
+ if ($this->_audioFormat != self::WAVE_FORMAT_PCM) {
533
+ $this->_factChunkSize = 4;
534
+ } else {
535
+ $this->_factChunkSize = 0;
536
+ }
537
+ } else {
538
+ $this->_factChunkSize = $factChunkSize;
539
+ }
540
+
541
+ $this->setChunkSize() // implicit setActualSize()
542
+ ->setDataOffset();
543
+
544
+ return $this;
545
+ }
546
+
547
+ public function getDataSize() {
548
+ return $this->_dataSize;
549
+ }
550
+
551
+ protected function setDataSize($dataSize = null) {
552
+ if (is_null($dataSize)) {
553
+ $this->_dataSize = strlen($this->_samples);
554
+ } else {
555
+ $this->_dataSize = $dataSize;
556
+ }
557
+
558
+ $this->setChunkSize() // implicit setActualSize()
559
+ ->setNumBlocks();
560
+ $this->_dataSize_valid = true;
561
+
562
+ return $this;
563
+ }
564
+
565
+ public function getDataOffset() {
566
+ return $this->_dataOffset;
567
+ }
568
+
569
+ protected function setDataOffset($dataOffset = null) {
570
+ if (is_null($dataOffset)) {
571
+ $this->_dataOffset = 8 + // "RIFF" header (ID + size)
572
+ 4 + // "WAVE" chunk
573
+ 8 + $this->_fmtChunkSize + // "fmt " subchunk
574
+ ($this->_factChunkSize > 0 ? 8 + $this->_factChunkSize : 0) + // "fact" subchunk
575
+ 8; // "data" subchunk
576
+ } else {
577
+ $this->_dataOffset = $dataOffset;
578
+ }
579
+
580
+ return $this;
581
+ }
582
+
583
+ public function getAudioFormat() {
584
+ return $this->_audioFormat;
585
+ }
586
+
587
+ protected function setAudioFormat($audioFormat = null) {
588
+ if (is_null($audioFormat)) {
589
+ if (($this->_bitsPerSample <= 16 || $this->_bitsPerSample == 32)
590
+ && $this->_validBitsPerSample == $this->_bitsPerSample
591
+ && $this->_channelMask == self::SPEAKER_DEFAULT
592
+ && $this->_numChannels <= 2) {
593
+ if ($this->_bitsPerSample <= 16) {
594
+ $this->_audioFormat = self::WAVE_FORMAT_PCM;
595
+ } else {
596
+ $this->_audioFormat = self::WAVE_FORMAT_IEEE_FLOAT;
597
+ }
598
+ } else {
599
+ $this->_audioFormat = self::WAVE_FORMAT_EXTENSIBLE;
600
+ }
601
+ } else {
602
+ $this->_audioFormat = $audioFormat;
603
+ }
604
+
605
+ $this->setAudioSubFormat()
606
+ ->setFactChunkSize() // implicit setSize(), setActualSize(), setDataOffset()
607
+ ->setFmtExtendedSize(); // implicit setFmtChunkSize(), setSize(), setActualSize(), setDataOffset()
608
+
609
+ return $this;
610
+ }
611
+
612
+ public function getAudioSubFormat() {
613
+ return $this->_audioSubFormat;
614
+ }
615
+
616
+ protected function setAudioSubFormat($audioSubFormat = null) {
617
+ if (is_null($audioSubFormat)) {
618
+ if ($this->_bitsPerSample == 32) {
619
+ $this->_audioSubFormat = self::WAVE_SUBFORMAT_IEEE_FLOAT; // 32 bits are IEEE FLOAT in this class
620
+ } else {
621
+ $this->_audioSubFormat = self::WAVE_SUBFORMAT_PCM; // 8, 16 and 24 bits are PCM in this class
622
+ }
623
+ } else {
624
+ $this->_audioSubFormat = $audioSubFormat;
625
+ }
626
+
627
+ return $this;
628
+ }
629
+
630
+ public function getNumChannels() {
631
+ return $this->_numChannels;
632
+ }
633
+
634
+ public function setNumChannels($numChannels) {
635
+ if ($numChannels < 1 || $numChannels > self::MAX_CHANNEL) {
636
+ throw new WavFileException('Unsupported number of channels. Only up to ' . self::MAX_CHANNEL . ' channels are supported.');
637
+ } elseif ($this->_samples !== '') {
638
+ trigger_error('Wav already has sample data. Changing the number of channels does not convert and may corrupt the data.', E_USER_NOTICE);
639
+ }
640
+
641
+ $this->_numChannels = (int)$numChannels;
642
+
643
+ $this->setAudioFormat() // implicit setAudioSubFormat(), setFactChunkSize(), setFmtExtendedSize(), setFmtChunkSize(), setSize(), setActualSize(), setDataOffset()
644
+ ->setByteRate()
645
+ ->setBlockAlign(); // implicit setNumBlocks()
646
+
647
+ return $this;
648
+ }
649
+
650
+ public function getChannelMask() {
651
+ return $this->_channelMask;
652
+ }
653
+
654
+ public function setChannelMask($channelMask = self::SPEAKER_DEFAULT) {
655
+ if ($channelMask != 0) {
656
+ // count number of set bits - Hamming weight
657
+ $c = (int)$channelMask;
658
+ $n = 0;
659
+ while ($c > 0) {
660
+ $n += $c & 1;
661
+ $c >>= 1;
662
+ }
663
+ if ($n != $this->_numChannels || (((int)$channelMask | self::SPEAKER_ALL) != self::SPEAKER_ALL)) {
664
+ throw new WavFileException('Invalid channel mask. The number of channels does not match the number of locations in the mask.');
665
+ }
666
+ }
667
+
668
+ $this->_channelMask = (int)$channelMask;
669
+
670
+ $this->setAudioFormat(); // implicit setAudioSubFormat(), setFactChunkSize(), setFmtExtendedSize(), setFmtChunkSize(), setSize(), setActualSize(), setDataOffset()
671
+
672
+ return $this;
673
+ }
674
+
675
+ public function getSampleRate() {
676
+ return $this->_sampleRate;
677
+ }
678
+
679
+ public function setSampleRate($sampleRate) {
680
+ if ($sampleRate < 1 || $sampleRate > self::MAX_SAMPLERATE) {
681
+ throw new WavFileException('Invalid sample rate.');
682
+ } elseif ($this->_samples !== '') {
683
+ trigger_error('Wav already has sample data. Changing the sample rate does not convert the data and may yield undesired results.', E_USER_NOTICE);
684
+ }
685
+
686
+ $this->_sampleRate = (int)$sampleRate;
687
+
688
+ $this->setByteRate();
689
+
690
+ return $this;
691
+ }
692
+
693
+ public function getBitsPerSample() {
694
+ return $this->_bitsPerSample;
695
+ }
696
+
697
+ public function setBitsPerSample($bitsPerSample) {
698
+ if (!in_array($bitsPerSample, array(8, 16, 24, 32))) {
699
+ throw new WavFileException('Unsupported bits per sample. Only 8, 16, 24 and 32 bits are supported.');
700
+ } elseif ($this->_samples !== '') {
701
+ trigger_error('Wav already has sample data. Changing the bits per sample does not convert and may corrupt the data.', E_USER_NOTICE);
702
+ }
703
+
704
+ $this->_bitsPerSample = (int)$bitsPerSample;
705
+
706
+ $this->setValidBitsPerSample() // implicit setAudioFormat(), setAudioSubFormat(), setFmtChunkSize(), setFactChunkSize(), setSize(), setActualSize(), setDataOffset()
707
+ ->setByteRate()
708
+ ->setBlockAlign(); // implicit setNumBlocks()
709
+
710
+ return $this;
711
+ }
712
+
713
+ public function getValidBitsPerSample() {
714
+ return $this->_validBitsPerSample;
715
+ }
716
+
717
+ protected function setValidBitsPerSample($validBitsPerSample = null) {
718
+ if (is_null($validBitsPerSample)) {
719
+ $this->_validBitsPerSample = $this->_bitsPerSample;
720
+ } else {
721
+ if ($validBitsPerSample < 1 || $validBitsPerSample > $this->_bitsPerSample) {
722
+ throw new WavFileException('ValidBitsPerSample cannot be greater than BitsPerSample.');
723
+ }
724
+ $this->_validBitsPerSample = (int)$validBitsPerSample;
725
+ }
726
+
727
+ $this->setAudioFormat(); // implicit setAudioSubFormat(), setFactChunkSize(), setFmtExtendedSize(), setFmtChunkSize(), setSize(), setActualSize(), setDataOffset()
728
+
729
+ return $this;
730
+ }
731
+
732
+ public function getBlockAlign() {
733
+ return $this->_blockAlign;
734
+ }
735
+
736
+ protected function setBlockAlign($blockAlign = null) {
737
+ if (is_null($blockAlign)) {
738
+ $this->_blockAlign = $this->_numChannels * $this->_bitsPerSample / 8;
739
+ } else {
740
+ $this->_blockAlign = $blockAlign;
741
+ }
742
+
743
+ $this->setNumBlocks();
744
+
745
+ return $this;
746
+ }
747
+
748
+ public function getNumBlocks()
749
+ {
750
+ return $this->_numBlocks;
751
+ }
752
+
753
+ protected function setNumBlocks($numBlocks = null) {
754
+ if (is_null($numBlocks)) {
755
+ $this->_numBlocks = (int)($this->_dataSize / $this->_blockAlign); // do not count incomplete sample blocks
756
+ } else {
757
+ $this->_numBlocks = $numBlocks;
758
+ }
759
+
760
+ return $this;
761
+ }
762
+
763
+ public function getByteRate() {
764
+ return $this->_byteRate;
765
+ }
766
+
767
+ protected function setByteRate($byteRate = null) {
768
+ if (is_null($byteRate)) {
769
+ $this->_byteRate = $this->_sampleRate * $this->_numChannels * $this->_bitsPerSample / 8;
770
+ } else {
771
+ $this->_byteRate = $byteRate;
772
+ }
773
+
774
+ return $this;
775
+ }
776
+
777
+ public function getSamples() {
778
+ return $this->_samples;
779
+ }
780
+
781
+ public function setSamples(&$samples = '') {
782
+ if (strlen($samples) % $this->_blockAlign != 0) {
783
+ throw new WavFileException('Incorrect samples size. Has to be a multiple of BlockAlign.');
784
+ }
785
+
786
+ $this->_samples = $samples;
787
+
788
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
789
+
790
+ return $this;
791
+ }
792
+
793
+
794
+ /*%******************************************************************************************%*/
795
+ // Getters
796
+
797
+ public function getMinAmplitude()
798
+ {
799
+ if ($this->_bitsPerSample == 8) {
800
+ return 0;
801
+ } elseif ($this->_bitsPerSample == 32) {
802
+ return -1.0;
803
+ } else {
804
+ return -(1 << ($this->_bitsPerSample - 1));
805
+ }
806
+ }
807
+
808
+ public function getZeroAmplitude()
809
+ {
810
+ if ($this->_bitsPerSample == 8) {
811
+ return 0x80;
812
+ } elseif ($this->_bitsPerSample == 32) {
813
+ return 0.0;
814
+ } else {
815
+ return 0;
816
+ }
817
+ }
818
+
819
+ public function getMaxAmplitude()
820
+ {
821
+ if($this->_bitsPerSample == 8) {
822
+ return 0xFF;
823
+ } elseif($this->_bitsPerSample == 32) {
824
+ return 1.0;
825
+ } else {
826
+ return (1 << ($this->_bitsPerSample - 1)) - 1;
827
+ }
828
+ }
829
+
830
+
831
+ /*%******************************************************************************************%*/
832
+ // Wave file methods
833
+
834
+ /**
835
+ * Construct a wav header from this object. Includes "fact" chunk in necessary.
836
+ * http://www-mmsp.ece.mcgill.ca/documents/audioformats/wave/wave.html
837
+ *
838
+ * @return string The RIFF header data.
839
+ */
840
+ public function makeHeader()
841
+ {
842
+ // reset and recalculate
843
+ $this->setAudioFormat(); // implicit setAudioSubFormat(), setFactChunkSize(), setFmtExtendedSize(), setFmtChunkSize(), setSize(), setActualSize(), setDataOffset()
844
+ $this->setNumBlocks();
845
+
846
+ // RIFF header
847
+ $header = pack('N', 0x52494646); // ChunkID - "RIFF"
848
+ $header .= pack('V', $this->getChunkSize()); // ChunkSize
849
+ $header .= pack('N', 0x57415645); // Format - "WAVE"
850
+
851
+ // "fmt " subchunk
852
+ $header .= pack('N', 0x666d7420); // SubchunkID - "fmt "
853
+ $header .= pack('V', $this->getFmtChunkSize()); // SubchunkSize
854
+ $header .= pack('v', $this->getAudioFormat()); // AudioFormat
855
+ $header .= pack('v', $this->getNumChannels()); // NumChannels
856
+ $header .= pack('V', $this->getSampleRate()); // SampleRate
857
+ $header .= pack('V', $this->getByteRate()); // ByteRate
858
+ $header .= pack('v', $this->getBlockAlign()); // BlockAlign
859
+ $header .= pack('v', $this->getBitsPerSample()); // BitsPerSample
860
+ if($this->getFmtExtendedSize() == 24) {
861
+ $header .= pack('v', 22); // extension size = 24 bytes, cbSize: 24 - 2 = 22 bytes
862
+ $header .= pack('v', $this->getValidBitsPerSample()); // ValidBitsPerSample
863
+ $header .= pack('V', $this->getChannelMask()); // ChannelMask
864
+ $header .= pack('H32', $this->getAudioSubFormat()); // SubFormat
865
+ } elseif ($this->getFmtExtendedSize() == 2) {
866
+ $header .= pack('v', 0); // extension size = 2 bytes, cbSize: 2 - 2 = 0 bytes
867
+ }
868
+
869
+ // "fact" subchunk
870
+ if ($this->getFactChunkSize() == 4) {
871
+ $header .= pack('N', 0x66616374); // SubchunkID - "fact"
872
+ $header .= pack('V', 4); // SubchunkSize
873
+ $header .= pack('V', $this->getNumBlocks()); // SampleLength (per channel)
874
+ }
875
+
876
+ return $header;
877
+ }
878
+
879
+ /**
880
+ * Construct wav DATA chunk.
881
+ *
882
+ * @return string The DATA header and chunk.
883
+ */
884
+ public function getDataSubchunk()
885
+ {
886
+ // check preconditions
887
+ if (!$this->_dataSize_valid) {
888
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
889
+ }
890
+
891
+
892
+ // create subchunk
893
+ return pack('N', 0x64617461) . // SubchunkID - "data"
894
+ pack('V', $this->getDataSize()) . // SubchunkSize
895
+ $this->_samples . // Subchunk data
896
+ ($this->getDataSize() & 1 ? chr(0) : ''); // padding byte
897
+ }
898
+
899
+ /**
900
+ * Save the wav data to a file.
901
+ *
902
+ * @param string $filename (Required) The file path to save the wav to.
903
+ * @throws WavFileException
904
+ */
905
+ public function save($filename)
906
+ {
907
+ $fp = @fopen($filename, 'w+b');
908
+ if (!is_resource($fp)) {
909
+ throw new WavFileException('Failed to open "' . $filename . '" for writing.');
910
+ }
911
+
912
+ fwrite($fp, $this->makeHeader());
913
+ fwrite($fp, $this->getDataSubchunk());
914
+ fclose($fp);
915
+
916
+ return $this;
917
+ }
918
+
919
+ /**
920
+ * Reads a wav header and data from a file.
921
+ *
922
+ * @param string $filename (Required) The path to the wav file to read.
923
+ * @param bool $readData (Optional) If true, also read the data chunk.
924
+ * @throws WavFormatException
925
+ * @throws WavFileException
926
+ */
927
+ public function openWav($filename, $readData = true)
928
+ {
929
+ // check preconditions
930
+ if (!file_exists($filename)) {
931
+ throw new WavFileException('Failed to open "' . $filename . '". File not found.');
932
+ } elseif (!is_readable($filename)) {
933
+ throw new WavFileException('Failed to open "' . $filename . '". File is not readable.');
934
+ } elseif (is_resource($this->_fp)) {
935
+ $this->closeWav();
936
+ }
937
+
938
+
939
+ // open the file
940
+ $this->_fp = @fopen($filename, 'rb');
941
+ if (!is_resource($this->_fp)) {
942
+ throw new WavFileException('Failed to open "' . $filename . '".');
943
+ }
944
+
945
+ // read the file
946
+ return $this->readWav($readData);
947
+ }
948
+
949
+ /**
950
+ * Close a with openWav() previously opened wav file or free the buffer of setWavData().
951
+ * Not necessary if the data has been read (readData = true) already.
952
+ */
953
+ public function closeWav() {
954
+ if (is_resource($this->_fp)) fclose($this->_fp);
955
+
956
+ return $this;
957
+ }
958
+
959
+ /**
960
+ * Set the wav file data and properties from a wav file in a string.
961
+ *
962
+ * @param string $data (Required) The wav file data. Passed by reference.
963
+ * @param bool $free (Optional) True to free the passed $data after copying.
964
+ * @throws WavFormatException
965
+ * @throws WavFileException
966
+ */
967
+ public function setWavData(&$data, $free = true)
968
+ {
969
+ // check preconditions
970
+ if (is_resource($this->_fp)) $this->closeWav();
971
+
972
+
973
+ // open temporary stream in memory
974
+ $this->_fp = @fopen('php://memory', 'w+b');
975
+ if (!is_resource($this->_fp)) {
976
+ throw new WavFileException('Failed to open memory stream to write wav data. Use openWav() instead.');
977
+ }
978
+
979
+ // prepare stream
980
+ fwrite($this->_fp, $data);
981
+ rewind($this->_fp);
982
+
983
+ // free the passed data
984
+ if ($free) $data = null;
985
+
986
+ // read the stream like a file
987
+ return $this->readWav(true);
988
+ }
989
+
990
+ /**
991
+ * Read wav file from a stream.
992
+ *
993
+ * @param $readData (Optional) If true, also read the data chunk.
994
+ * @throws WavFormatException
995
+ * @throws WavFileException
996
+ */
997
+ protected function readWav($readData = true)
998
+ {
999
+ if (!is_resource($this->_fp)) {
1000
+ throw new WavFileException('No wav file open. Use openWav() first.');
1001
+ }
1002
+
1003
+ try {
1004
+ $this->readWavHeader();
1005
+ } catch (WavFileException $ex) {
1006
+ $this->closeWav();
1007
+ throw $ex;
1008
+ }
1009
+
1010
+ if ($readData) return $this->readWavData();
1011
+
1012
+ return $this;
1013
+ }
1014
+
1015
+ /**
1016
+ * Parse a wav header.
1017
+ * http://www-mmsp.ece.mcgill.ca/documents/audioformats/wave/wave.html
1018
+ *
1019
+ * @throws WavFormatException
1020
+ * @throws WavFileException
1021
+ */
1022
+ protected function readWavHeader()
1023
+ {
1024
+ if (!is_resource($this->_fp)) {
1025
+ throw new WavFileException('No wav file open. Use openWav() first.');
1026
+ }
1027
+
1028
+ // get actual file size
1029
+ $stat = fstat($this->_fp);
1030
+ $actualSize = $stat['size'];
1031
+
1032
+ $this->_actualSize = $actualSize;
1033
+
1034
+
1035
+ // read the common header
1036
+ $header = fread($this->_fp, 36); // minimum size of the wav header
1037
+ if (strlen($header) < 36) {
1038
+ throw new WavFormatException('Not wav format. Header too short.', 1);
1039
+ }
1040
+
1041
+
1042
+ // check "RIFF" header
1043
+ $RIFF = unpack('NChunkID/VChunkSize/NFormat', $header);
1044
+
1045
+ if ($RIFF['ChunkID'] != 0x52494646) { // "RIFF"
1046
+ throw new WavFormatException('Not wav format. "RIFF" signature missing.', 2);
1047
+ }
1048
+
1049
+ if ($actualSize - 8 < $RIFF['ChunkSize']) {
1050
+ trigger_error('"RIFF" chunk size does not match actual file size. Found ' . $RIFF['ChunkSize'] . ', expected ' . ($actualSize - 8) . '.', E_USER_NOTICE);
1051
+ $RIFF['ChunkSize'] = $actualSize - 8;
1052
+ //throw new WavFormatException('"RIFF" chunk size does not match actual file size. Found ' . $RIFF['ChunkSize'] . ', expected ' . ($actualSize - 8) . '.', 3);
1053
+ }
1054
+
1055
+ if ($RIFF['Format'] != 0x57415645) { // "WAVE"
1056
+ throw new WavFormatException('Not wav format. "RIFF" chunk format is not "WAVE".', 4);
1057
+ }
1058
+
1059
+ $this->_chunkSize = $RIFF['ChunkSize'];
1060
+
1061
+
1062
+ // check common "fmt " subchunk
1063
+ $fmt = unpack('NSubchunkID/VSubchunkSize/vAudioFormat/vNumChannels/'
1064
+ .'VSampleRate/VByteRate/vBlockAlign/vBitsPerSample',
1065
+ substr($header, 12));
1066
+
1067
+ if ($fmt['SubchunkID'] != 0x666d7420) { // "fmt "
1068
+ throw new WavFormatException('Bad wav header. Expected "fmt " subchunk.', 11);
1069
+ }
1070
+
1071
+ if ($fmt['SubchunkSize'] < 16) {
1072
+ throw new WavFormatException('Bad "fmt " subchunk size.', 12);
1073
+ }
1074
+
1075
+ if ( $fmt['AudioFormat'] != self::WAVE_FORMAT_PCM
1076
+ && $fmt['AudioFormat'] != self::WAVE_FORMAT_IEEE_FLOAT
1077
+ && $fmt['AudioFormat'] != self::WAVE_FORMAT_EXTENSIBLE)
1078
+ {
1079
+ throw new WavFormatException('Unsupported audio format. Only PCM or IEEE FLOAT (EXTENSIBLE) audio is supported.', 13);
1080
+ }
1081
+
1082
+ if ($fmt['NumChannels'] < 1 || $fmt['NumChannels'] > self::MAX_CHANNEL) {
1083
+ throw new WavFormatException('Invalid number of channels in "fmt " subchunk.', 14);
1084
+ }
1085
+
1086
+ if ($fmt['SampleRate'] < 1 || $fmt['SampleRate'] > self::MAX_SAMPLERATE) {
1087
+ throw new WavFormatException('Invalid sample rate in "fmt " subchunk.', 15);
1088
+ }
1089
+
1090
+ if ( ($fmt['AudioFormat'] == self::WAVE_FORMAT_PCM && !in_array($fmt['BitsPerSample'], array(8, 16, 24)))
1091
+ || ($fmt['AudioFormat'] == self::WAVE_FORMAT_IEEE_FLOAT && $fmt['BitsPerSample'] != 32)
1092
+ || ($fmt['AudioFormat'] == self::WAVE_FORMAT_EXTENSIBLE && !in_array($fmt['BitsPerSample'], array(8, 16, 24, 32))))
1093
+ {
1094
+ throw new WavFormatException('Only 8, 16 and 24-bit PCM and 32-bit IEEE FLOAT (EXTENSIBLE) audio is supported.', 16);
1095
+ }
1096
+
1097
+ $blockAlign = $fmt['NumChannels'] * $fmt['BitsPerSample'] / 8;
1098
+ if ($blockAlign != $fmt['BlockAlign']) {
1099
+ trigger_error('Invalid block align in "fmt " subchunk. Found ' . $fmt['BlockAlign'] . ', expected ' . $blockAlign . '.', E_USER_NOTICE);
1100
+ $fmt['BlockAlign'] = $blockAlign;
1101
+ //throw new WavFormatException('Invalid block align in "fmt " subchunk. Found ' . $fmt['BlockAlign'] . ', expected ' . $blockAlign . '.', 17);
1102
+ }
1103
+
1104
+ $byteRate = $fmt['SampleRate'] * $blockAlign;
1105
+ if ($byteRate != $fmt['ByteRate']) {
1106
+ trigger_error('Invalid average byte rate in "fmt " subchunk. Found ' . $fmt['ByteRate'] . ', expected ' . $byteRate . '.', E_USER_NOTICE);
1107
+ $fmt['ByteRate'] = $byteRate;
1108
+ //throw new WavFormatException('Invalid average byte rate in "fmt " subchunk. Found ' . $fmt['ByteRate'] . ', expected ' . $byteRate . '.', 18);
1109
+ }
1110
+
1111
+ $this->_fmtChunkSize = $fmt['SubchunkSize'];
1112
+ $this->_audioFormat = $fmt['AudioFormat'];
1113
+ $this->_numChannels = $fmt['NumChannels'];
1114
+ $this->_sampleRate = $fmt['SampleRate'];
1115
+ $this->_byteRate = $fmt['ByteRate'];
1116
+ $this->_blockAlign = $fmt['BlockAlign'];
1117
+ $this->_bitsPerSample = $fmt['BitsPerSample'];
1118
+
1119
+
1120
+ // read extended "fmt " subchunk data
1121
+ $extendedFmt = '';
1122
+ if ($fmt['SubchunkSize'] > 16) {
1123
+ // possibly handle malformed subchunk without a padding byte
1124
+ $extendedFmt = fread($this->_fp, $fmt['SubchunkSize'] - 16 + ($fmt['SubchunkSize'] & 1)); // also read padding byte
1125
+ if (strlen($extendedFmt) < $fmt['SubchunkSize'] - 16) {
1126
+ throw new WavFormatException('Not wav format. Header too short.', 1);
1127
+ }
1128
+ }
1129
+
1130
+
1131
+ // check extended "fmt " for EXTENSIBLE Audio Format
1132
+ if ($fmt['AudioFormat'] == self::WAVE_FORMAT_EXTENSIBLE) {
1133
+ if (strlen($extendedFmt) < 24) {
1134
+ throw new WavFormatException('Invalid EXTENSIBLE "fmt " subchunk size. Found ' . $fmt['SubchunkSize'] . ', expected at least 40.', 19);
1135
+ }
1136
+
1137
+ $extensibleFmt = unpack('vSize/vValidBitsPerSample/VChannelMask/H32SubFormat', substr($extendedFmt, 0, 24));
1138
+
1139
+ if ( $extensibleFmt['SubFormat'] != self::WAVE_SUBFORMAT_PCM
1140
+ && $extensibleFmt['SubFormat'] != self::WAVE_SUBFORMAT_IEEE_FLOAT)
1141
+ {
1142
+ throw new WavFormatException('Unsupported audio format. Only PCM or IEEE FLOAT (EXTENSIBLE) audio is supported.', 13);
1143
+ }
1144
+
1145
+ if ( ($extensibleFmt['SubFormat'] == self::WAVE_SUBFORMAT_PCM && !in_array($fmt['BitsPerSample'], array(8, 16, 24)))
1146
+ || ($extensibleFmt['SubFormat'] == self::WAVE_SUBFORMAT_IEEE_FLOAT && $fmt['BitsPerSample'] != 32))
1147
+ {
1148
+ throw new WavFormatException('Only 8, 16 and 24-bit PCM and 32-bit IEEE FLOAT (EXTENSIBLE) audio is supported.', 16);
1149
+ }
1150
+
1151
+ if ($extensibleFmt['Size'] != 22) {
1152
+ trigger_error('Invaid extension size in EXTENSIBLE "fmt " subchunk.', E_USER_NOTICE);
1153
+ $extensibleFmt['Size'] = 22;
1154
+ //throw new WavFormatException('Invaid extension size in EXTENSIBLE "fmt " subchunk.', 20);
1155
+ }
1156
+
1157
+ if ($extensibleFmt['ValidBitsPerSample'] != $fmt['BitsPerSample']) {
1158
+ trigger_error('Invaid or unsupported valid bits per sample in EXTENSIBLE "fmt " subchunk.', E_USER_NOTICE);
1159
+ $extensibleFmt['ValidBitsPerSample'] = $fmt['BitsPerSample'];
1160
+ //throw new WavFormatException('Invaid or unsupported valid bits per sample in EXTENSIBLE "fmt " subchunk.', 21);
1161
+ }
1162
+
1163
+ if ($extensibleFmt['ChannelMask'] != 0) {
1164
+ // count number of set bits - Hamming weight
1165
+ $c = (int)$extensibleFmt['ChannelMask'];
1166
+ $n = 0;
1167
+ while ($c > 0) {
1168
+ $n += $c & 1;
1169
+ $c >>= 1;
1170
+ }
1171
+ if ($n != $fmt['NumChannels'] || (((int)$extensibleFmt['ChannelMask'] | self::SPEAKER_ALL) != self::SPEAKER_ALL)) {
1172
+ trigger_error('Invalid channel mask in EXTENSIBLE "fmt " subchunk. The number of channels does not match the number of locations in the mask.', E_USER_NOTICE);
1173
+ $extensibleFmt['ChannelMask'] = 0;
1174
+ //throw new WavFormatException('Invalid channel mask in EXTENSIBLE "fmt " subchunk. The number of channels does not match the number of locations in the mask.', 22);
1175
+ }
1176
+ }
1177
+
1178
+ $this->_fmtExtendedSize = strlen($extendedFmt);
1179
+ $this->_validBitsPerSample = $extensibleFmt['ValidBitsPerSample'];
1180
+ $this->_channelMask = $extensibleFmt['ChannelMask'];
1181
+ $this->_audioSubFormat = $extensibleFmt['SubFormat'];
1182
+
1183
+ } else {
1184
+ $this->_fmtExtendedSize = strlen($extendedFmt);
1185
+ $this->_validBitsPerSample = $fmt['BitsPerSample'];
1186
+ $this->_channelMask = 0;
1187
+ $this->_audioSubFormat = null;
1188
+ }
1189
+
1190
+
1191
+ // read additional subchunks until "data" subchunk is found
1192
+ $factSubchunk = array();
1193
+ $dataSubchunk = array();
1194
+
1195
+ while (!feof($this->_fp)) {
1196
+ $subchunkHeader = fread($this->_fp, 8);
1197
+ if (strlen($subchunkHeader) < 8) {
1198
+ throw new WavFormatException('Missing "data" subchunk.', 101);
1199
+ }
1200
+
1201
+ $subchunk = unpack('NSubchunkID/VSubchunkSize', $subchunkHeader);
1202
+
1203
+ if ($subchunk['SubchunkID'] == 0x66616374) { // "fact"
1204
+ // possibly handle malformed subchunk without a padding byte
1205
+ $subchunkData = fread($this->_fp, $subchunk['SubchunkSize'] + ($subchunk['SubchunkSize'] & 1)); // also read padding byte
1206
+ if (strlen($subchunkData) < 4) {
1207
+ throw new WavFormatException('Invalid "fact" subchunk.', 102);
1208
+ }
1209
+
1210
+ $factParams = unpack('VSampleLength', substr($subchunkData, 0, 4));
1211
+ $factSubchunk = array_merge($subchunk, $factParams);
1212
+
1213
+ } elseif ($subchunk['SubchunkID'] == 0x64617461) { // "data"
1214
+ $dataSubchunk = $subchunk;
1215
+
1216
+ break;
1217
+
1218
+ } elseif ($subchunk['SubchunkID'] == 0x7761766C) { // "wavl"
1219
+ throw new WavFormatException('Wave List Chunk ("wavl" subchunk) is not supported.', 106);
1220
+ } else {
1221
+ // skip all other (unknown) subchunks
1222
+ // possibly handle malformed subchunk without a padding byte
1223
+ if ( $subchunk['SubchunkSize'] < 0
1224
+ || fseek($this->_fp, $subchunk['SubchunkSize'] + ($subchunk['SubchunkSize'] & 1), SEEK_CUR) !== 0) { // also skip padding byte
1225
+ throw new WavFormatException('Invalid subchunk (0x' . dechex($subchunk['SubchunkID']) . ') encountered.', 103);
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ if (empty($dataSubchunk)) {
1231
+ throw new WavFormatException('Missing "data" subchunk.', 101);
1232
+ }
1233
+
1234
+
1235
+ // check "data" subchunk
1236
+ $dataOffset = ftell($this->_fp);
1237
+ if ($dataSubchunk['SubchunkSize'] < 0 || $actualSize - $dataOffset < $dataSubchunk['SubchunkSize']) {
1238
+ trigger_error('Invalid "data" subchunk size.', E_USER_NOTICE);
1239
+ $dataSubchunk['SubchunkSize'] = $actualSize - $dataOffset;
1240
+ //throw new WavFormatException('Invalid "data" subchunk size.', 104);
1241
+ }
1242
+
1243
+ $this->_dataOffset = $dataOffset;
1244
+ $this->_dataSize = $dataSubchunk['SubchunkSize'];
1245
+ $this->_dataSize_fp = $dataSubchunk['SubchunkSize'];
1246
+ $this->_dataSize_valid = false;
1247
+ $this->_samples = '';
1248
+
1249
+
1250
+ // check "fact" subchunk
1251
+ $numBlocks = (int)($dataSubchunk['SubchunkSize'] / $fmt['BlockAlign']);
1252
+
1253
+ if (empty($factSubchunk)) { // construct fake "fact" subchunk
1254
+ $factSubchunk = array('SubchunkSize' => 0, 'SampleLength' => $numBlocks);
1255
+ }
1256
+
1257
+ if ($factSubchunk['SampleLength'] != $numBlocks) {
1258
+ trigger_error('Invalid sample length in "fact" subchunk.', E_USER_NOTICE);
1259
+ $factSubchunk['SampleLength'] = $numBlocks;
1260
+ //throw new WavFormatException('Invalid sample length in "fact" subchunk.', 105);
1261
+ }
1262
+
1263
+ $this->_factChunkSize = $factSubchunk['SubchunkSize'];
1264
+ $this->_numBlocks = $factSubchunk['SampleLength'];
1265
+
1266
+
1267
+ return $this;
1268
+
1269
+ }
1270
+
1271
+ /**
1272
+ * Read the wav data from the file into the buffer.
1273
+ *
1274
+ * @param $dataOffset (Optional) The byte offset to skip before starting to read. Must be a multiple of BlockAlign.
1275
+ * @param $dataSize (Optional) The size of the data to read in bytes. Must be a multiple of BlockAlign. Defaults to all data.
1276
+ * @throws WavFileException
1277
+ */
1278
+ public function readWavData($dataOffset = 0, $dataSize = null)
1279
+ {
1280
+ // check preconditions
1281
+ if (!is_resource($this->_fp)) {
1282
+ throw new WavFileException('No wav file open. Use openWav() first.');
1283
+ }
1284
+
1285
+ if ($dataOffset < 0 || $dataOffset % $this->getBlockAlign() > 0) {
1286
+ throw new WavFileException('Invalid data offset. Has to be a multiple of BlockAlign.');
1287
+ }
1288
+
1289
+ if (is_null($dataSize)) {
1290
+ $dataSize = $this->_dataSize_fp - ($this->_dataSize_fp % $this->getBlockAlign()); // only read complete blocks
1291
+ } elseif ($dataSize < 0 || $dataSize % $this->getBlockAlign() > 0) {
1292
+ throw new WavFileException('Invalid data size to read. Has to be a multiple of BlockAlign.');
1293
+ }
1294
+
1295
+
1296
+ // skip offset
1297
+ if ($dataOffset > 0 && fseek($this->_fp, $dataOffset, SEEK_CUR) !== 0) {
1298
+ throw new WavFileException('Seeking to data offset failed.');
1299
+ }
1300
+
1301
+ // read data
1302
+ $this->_samples .= fread($this->_fp, $dataSize); // allow appending
1303
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
1304
+
1305
+ // close file or memory stream
1306
+ return $this->closeWav();
1307
+ }
1308
+
1309
+
1310
+ /*%******************************************************************************************%*/
1311
+ // Sample manipulation methods
1312
+
1313
+ /**
1314
+ * Return a single sample block from the file.
1315
+ *
1316
+ * @param int $blockNum (Required) The sample block number. Zero based.
1317
+ * @return string The binary sample block (all channels). Returns null if the sample block number was out of range.
1318
+ */
1319
+ public function getSampleBlock($blockNum)
1320
+ {
1321
+ // check preconditions
1322
+ if (!$this->_dataSize_valid) {
1323
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
1324
+ }
1325
+
1326
+ $offset = $blockNum * $this->_blockAlign;
1327
+ if ($offset + $this->_blockAlign > $this->_dataSize || $offset < 0) {
1328
+ return null;
1329
+ }
1330
+
1331
+
1332
+ // read data
1333
+ return substr($this->_samples, $offset, $this->_blockAlign);
1334
+ }
1335
+
1336
+ /**
1337
+ * Set a single sample block. <br />
1338
+ * Allows to append a sample block.
1339
+ *
1340
+ * @param string $sampleBlock (Required) The binary sample block (all channels).
1341
+ * @param int $blockNum (Required) The sample block number. Zero based.
1342
+ * @throws WavFileException
1343
+ */
1344
+ public function setSampleBlock($sampleBlock, $blockNum)
1345
+ {
1346
+ // check preconditions
1347
+ $blockAlign = $this->_blockAlign;
1348
+ if (!isset($sampleBlock[$blockAlign - 1]) || isset($sampleBlock[$blockAlign])) { // faster than: if (strlen($sampleBlock) != $blockAlign)
1349
+ throw new WavFileException('Incorrect sample block size. Got ' . strlen($sampleBlock) . ', expected ' . $blockAlign . '.');
1350
+ }
1351
+
1352
+ if (!$this->_dataSize_valid) {
1353
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
1354
+ }
1355
+
1356
+ $numBlocks = (int)($this->_dataSize / $blockAlign);
1357
+ $offset = $blockNum * $blockAlign;
1358
+ if ($blockNum > $numBlocks || $blockNum < 0) { // allow appending
1359
+ throw new WavFileException('Sample block number is out of range.');
1360
+ }
1361
+
1362
+
1363
+ // replace or append data
1364
+ if ($blockNum == $numBlocks) {
1365
+ // append
1366
+ $this->_samples .= $sampleBlock;
1367
+ $this->_dataSize += $blockAlign;
1368
+ $this->_chunkSize += $blockAlign;
1369
+ $this->_actualSize += $blockAlign;
1370
+ $this->_numBlocks++;
1371
+ } else {
1372
+ // replace
1373
+ for ($i = 0; $i < $blockAlign; ++$i) {
1374
+ $this->_samples[$offset + $i] = $sampleBlock[$i];
1375
+ }
1376
+ }
1377
+
1378
+ return $this;
1379
+ }
1380
+
1381
+ /**
1382
+ * Get a float sample value for a specific sample block and channel number.
1383
+ *
1384
+ * @param int $blockNum (Required) The sample block number to fetch. Zero based.
1385
+ * @param int $channelNum (Required) The channel number within the sample block to fetch. First channel is 1.
1386
+ * @return float The float sample value. Returns null if the sample block number was out of range.
1387
+ * @throws WavFileException
1388
+ */
1389
+ public function getSampleValue($blockNum, $channelNum)
1390
+ {
1391
+ // check preconditions
1392
+ if ($channelNum < 1 || $channelNum > $this->_numChannels) {
1393
+ throw new WavFileException('Channel number is out of range.');
1394
+ }
1395
+
1396
+ if (!$this->_dataSize_valid) {
1397
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
1398
+ }
1399
+
1400
+ $sampleBytes = $this->_bitsPerSample / 8;
1401
+ $offset = $blockNum * $this->_blockAlign + ($channelNum - 1) * $sampleBytes;
1402
+ if ($offset + $sampleBytes > $this->_dataSize || $offset < 0) {
1403
+ return null;
1404
+ }
1405
+
1406
+ // read binary value
1407
+ $sampleBinary = substr($this->_samples, $offset, $sampleBytes);
1408
+
1409
+ // convert binary to value
1410
+ switch ($this->_bitsPerSample) {
1411
+ case 8:
1412
+ // unsigned char
1413
+ return (float)((ord($sampleBinary) - 0x80) / 0x80);
1414
+
1415
+ case 16:
1416
+ // signed short, little endian
1417
+ $data = unpack('v', $sampleBinary);
1418
+ $sample = $data[1];
1419
+ if ($sample >= 0x8000) {
1420
+ $sample -= 0x10000;
1421
+ }
1422
+ return (float)($sample / 0x8000);
1423
+
1424
+ case 24:
1425
+ // 3 byte packed signed integer, little endian
1426
+ $data = unpack('C3', $sampleBinary);
1427
+ $sample = $data[1] | ($data[2] << 8) | ($data[3] << 16);
1428
+ if ($sample >= 0x800000) {
1429
+ $sample -= 0x1000000;
1430
+ }
1431
+ return (float)($sample / 0x800000);
1432
+
1433
+ case 32:
1434
+ // 32-bit float
1435
+ $data = unpack('f', $sampleBinary);
1436
+ return (float)$data[1];
1437
+
1438
+ default:
1439
+ return null;
1440
+ }
1441
+ }
1442
+
1443
+ /**
1444
+ * Sets a float sample value for a specific sample block number and channel. <br />
1445
+ * Converts float values to appropriate integer values and clips properly. <br />
1446
+ * Allows to append samples (in order).
1447
+ *
1448
+ * @param float $sampleFloat (Required) The float sample value to set. Converts float values and clips if necessary.
1449
+ * @param int $blockNum (Required) The sample block number to set or append. Zero based.
1450
+ * @param int $channelNum (Required) The channel number within the sample block to set or append. First channel is 1.
1451
+ * @throws WavFileException
1452
+ */
1453
+ public function setSampleValue($sampleFloat, $blockNum, $channelNum)
1454
+ {
1455
+ // check preconditions
1456
+ if ($channelNum < 1 || $channelNum > $this->_numChannels) {
1457
+ throw new WavFileException('Channel number is out of range.');
1458
+ }
1459
+
1460
+ if (!$this->_dataSize_valid) {
1461
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
1462
+ }
1463
+
1464
+ $dataSize = $this->_dataSize;
1465
+ $bitsPerSample = $this->_bitsPerSample;
1466
+ $sampleBytes = $bitsPerSample / 8;
1467
+ $offset = $blockNum * $this->_blockAlign + ($channelNum - 1) * $sampleBytes;
1468
+ if (($offset + $sampleBytes > $dataSize && $offset != $dataSize) || $offset < 0) { // allow appending
1469
+ throw new WavFileException('Sample block or channel number is out of range.');
1470
+ }
1471
+
1472
+
1473
+ // convert to value, quantize and clip
1474
+ if ($bitsPerSample == 32) {
1475
+ $sample = $sampleFloat < -1.0 ? -1.0 : ($sampleFloat > 1.0 ? 1.0 : $sampleFloat);
1476
+ } else {
1477
+ $p = 1 << ($bitsPerSample - 1); // 2 to the power of _bitsPerSample divided by 2
1478
+
1479
+ // project and quantize (round) float to integer values
1480
+ $sample = $sampleFloat < 0 ? (int)($sampleFloat * $p - 0.5) : (int)($sampleFloat * $p + 0.5);
1481
+
1482
+ // clip if necessary to [-$p, $p - 1]
1483
+ if ($sample < -$p) {
1484
+ $sample = -$p;
1485
+ } elseif ($sample > $p - 1) {
1486
+ $sample = $p - 1;
1487
+ }
1488
+ }
1489
+
1490
+ // convert to binary
1491
+ switch ($bitsPerSample) {
1492
+ case 8:
1493
+ // unsigned char
1494
+ $sampleBinary = chr($sample + 0x80);
1495
+ break;
1496
+
1497
+ case 16:
1498
+ // signed short, little endian
1499
+ if ($sample < 0) {
1500
+ $sample += 0x10000;
1501
+ }
1502
+ $sampleBinary = pack('v', $sample);
1503
+ break;
1504
+
1505
+ case 24:
1506
+ // 3 byte packed signed integer, little endian
1507
+ if ($sample < 0) {
1508
+ $sample += 0x1000000;
1509
+ }
1510
+ $sampleBinary = pack('C3', $sample & 0xff, ($sample >> 8) & 0xff, ($sample >> 16) & 0xff);
1511
+ break;
1512
+
1513
+ case 32:
1514
+ // 32-bit float
1515
+ $sampleBinary = pack('f', $sample);
1516
+ break;
1517
+
1518
+ default:
1519
+ $sampleBinary = null;
1520
+ $sampleBytes = 0;
1521
+ break;
1522
+ }
1523
+
1524
+ // replace or append data
1525
+ if ($offset == $dataSize) {
1526
+ // append
1527
+ $this->_samples .= $sampleBinary;
1528
+ $this->_dataSize += $sampleBytes;
1529
+ $this->_chunkSize += $sampleBytes;
1530
+ $this->_actualSize += $sampleBytes;
1531
+ $this->_numBlocks = (int)($this->_dataSize / $this->_blockAlign);
1532
+ } else {
1533
+ // replace
1534
+ for ($i = 0; $i < $sampleBytes; ++$i) {
1535
+ $this->_samples{$offset + $i} = $sampleBinary{$i};
1536
+ }
1537
+ }
1538
+
1539
+ return $this;
1540
+ }
1541
+
1542
+
1543
+ /*%******************************************************************************************%*/
1544
+ // Audio processing methods
1545
+
1546
+ /**
1547
+ * Run samples through audio processing filters.
1548
+ *
1549
+ * <code>
1550
+ * $wav->filter(
1551
+ * array(
1552
+ * WavFile::FILTER_MIX => array( // Filter for mixing 2 WavFile instances.
1553
+ * 'wav' => $wav2, // (Required) The WavFile to mix into this WhavFile. If no optional arguments are given, can be passed without the array.
1554
+ * 'loop' => true, // (Optional) Loop the selected portion (with warping to the beginning at the end).
1555
+ * 'blockOffset' => 0, // (Optional) Block number to start mixing from.
1556
+ * 'numBlocks' => null // (Optional) Number of blocks to mix in or to select for looping. Defaults to the end or all data for looping.
1557
+ * ),
1558
+ * WavFile::FILTER_NORMALIZE => 0.6, // (Required) Normalization of (mixed) audio samples - see threshold parameter for normalizeSample().
1559
+ * WavFile::FILTER_DEGRADE => 0.9 // (Required) Introduce random noise. The quality relative to the amplitude. 1 = no noise, 0 = max. noise.
1560
+ * ),
1561
+ * 0, // (Optional) The block number of this WavFile to start with.
1562
+ * null // (Optional) The number of blocks to process.
1563
+ * );
1564
+ * </code>
1565
+ *
1566
+ * @param array $filters (Required) An array of 1 or more audio processing filters.
1567
+ * @param int $blockOffset (Optional) The block number to start precessing from.
1568
+ * @param int $numBlocks (Optional) The maximum number of blocks to process.
1569
+ * @throws WavFileException
1570
+ */
1571
+ public function filter($filters, $blockOffset = 0, $numBlocks = null)
1572
+ {
1573
+ // check preconditions
1574
+ $totalBlocks = $this->getNumBlocks();
1575
+ $numChannels = $this->getNumChannels();
1576
+ if (is_null($numBlocks)) $numBlocks = $totalBlocks - $blockOffset;
1577
+
1578
+ if (!is_array($filters) || empty($filters) || $blockOffset < 0 || $blockOffset > $totalBlocks || $numBlocks <= 0) {
1579
+ // nothing to do
1580
+ return $this;
1581
+ }
1582
+
1583
+ // check filtes
1584
+ $filter_mix = false;
1585
+ if (array_key_exists(self::FILTER_MIX, $filters)) {
1586
+ if (!is_array($filters[self::FILTER_MIX])) {
1587
+ // assume the 'wav' parameter
1588
+ $filters[self::FILTER_MIX] = array('wav' => $filters[self::FILTER_MIX]);
1589
+ }
1590
+
1591
+ $mix_wav = @$filters[self::FILTER_MIX]['wav'];
1592
+ if (!($mix_wav instanceof WavFile)) {
1593
+ throw new WavFileException("WavFile to mix is missing or invalid.");
1594
+ } elseif ($mix_wav->getSampleRate() != $this->getSampleRate()) {
1595
+ throw new WavFileException("Sample rate of WavFile to mix does not match.");
1596
+ } else if ($mix_wav->getNumChannels() != $this->getNumChannels()) {
1597
+ throw new WavFileException("Number of channels of WavFile to mix does not match.");
1598
+ }
1599
+
1600
+ $mix_loop = @$filters[self::FILTER_MIX]['loop'];
1601
+ if (is_null($mix_loop)) $mix_loop = false;
1602
+
1603
+ $mix_blockOffset = @$filters[self::FILTER_MIX]['blockOffset'];
1604
+ if (is_null($mix_blockOffset)) $mix_blockOffset = 0;
1605
+
1606
+ $mix_totalBlocks = $mix_wav->getNumBlocks();
1607
+ $mix_numBlocks = @$filters[self::FILTER_MIX]['numBlocks'];
1608
+ if (is_null($mix_numBlocks)) $mix_numBlocks = $mix_loop ? $mix_totalBlocks : $mix_totalBlocks - $mix_blockOffset;
1609
+ $mix_maxBlock = min($mix_blockOffset + $mix_numBlocks, $mix_totalBlocks);
1610
+
1611
+ $filter_mix = true;
1612
+ }
1613
+
1614
+ $filter_normalize = false;
1615
+ if (array_key_exists(self::FILTER_NORMALIZE, $filters)) {
1616
+ $normalize_threshold = @$filters[self::FILTER_NORMALIZE];
1617
+
1618
+ if (!is_null($normalize_threshold) && abs($normalize_threshold) != 1) $filter_normalize = true;
1619
+ }
1620
+
1621
+ $filter_degrade = false;
1622
+ if (array_key_exists(self::FILTER_DEGRADE, $filters)) {
1623
+ $degrade_quality = @$filters[self::FILTER_DEGRADE];
1624
+ if (is_null($degrade_quality)) $degrade_quality = 1;
1625
+
1626
+ if ($degrade_quality >= 0 && $degrade_quality < 1) $filter_degrade = true;
1627
+ }
1628
+
1629
+
1630
+ // loop through all sample blocks
1631
+ for ($block = 0; $block < $numBlocks; ++$block) {
1632
+ // loop through all channels
1633
+ for ($channel = 1; $channel <= $numChannels; ++$channel) {
1634
+ // read current sample
1635
+ $currentBlock = $blockOffset + $block;
1636
+ $sampleFloat = $this->getSampleValue($currentBlock, $channel);
1637
+
1638
+
1639
+ /************* MIX FILTER ***********************/
1640
+ if ($filter_mix) {
1641
+ if ($mix_loop) {
1642
+ $mixBlock = ($mix_blockOffset + ($block % $mix_numBlocks)) % $mix_totalBlocks;
1643
+ } else {
1644
+ $mixBlock = $mix_blockOffset + $block;
1645
+ }
1646
+
1647
+ if ($mixBlock < $mix_maxBlock) {
1648
+ $sampleFloat += $mix_wav->getSampleValue($mixBlock, $channel);
1649
+ }
1650
+ }
1651
+
1652
+ /************* NORMALIZE FILTER *******************/
1653
+ if ($filter_normalize) {
1654
+ $sampleFloat = $this->normalizeSample($sampleFloat, $normalize_threshold);
1655
+ }
1656
+
1657
+ /************* DEGRADE FILTER *******************/
1658
+ if ($filter_degrade) {
1659
+ $sampleFloat += rand(1000000 * ($degrade_quality - 1), 1000000 * (1 - $degrade_quality)) / 1000000;
1660
+ }
1661
+
1662
+
1663
+ // write current sample
1664
+ $this->setSampleValue($sampleFloat, $currentBlock, $channel);
1665
+ }
1666
+ }
1667
+
1668
+ return $this;
1669
+ }
1670
+
1671
+ /**
1672
+ * Append a wav file to the current wav. <br />
1673
+ * The wav files must have the same sample rate, number of bits per sample, and number of channels.
1674
+ *
1675
+ * @param WavFile $wav (Required) The wav file to append.
1676
+ * @throws WavFileException
1677
+ */
1678
+ public function appendWav(WavFile $wav) {
1679
+ // basic checks
1680
+ if ($wav->getSampleRate() != $this->getSampleRate()) {
1681
+ throw new WavFileException("Sample rate for wav files do not match.");
1682
+ } else if ($wav->getBitsPerSample() != $this->getBitsPerSample()) {
1683
+ throw new WavFileException("Bits per sample for wav files do not match.");
1684
+ } else if ($wav->getNumChannels() != $this->getNumChannels()) {
1685
+ throw new WavFileException("Number of channels for wav files do not match.");
1686
+ }
1687
+
1688
+ $this->_samples .= $wav->_samples;
1689
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
1690
+
1691
+ return $this;
1692
+ }
1693
+
1694
+ /**
1695
+ * Mix 2 wav files together. <br />
1696
+ * Both wavs must have the same sample rate and same number of channels.
1697
+ *
1698
+ * @param WavFile $wav (Required) The WavFile to mix.
1699
+ * @param float $normalizeThreshold (Optional) See normalizeSample for an explanation.
1700
+ * @throws WavFileException
1701
+ */
1702
+ public function mergeWav(WavFile $wav, $normalizeThreshold = null) {
1703
+ return $this->filter(array(
1704
+ WavFile::FILTER_MIX => $wav,
1705
+ WavFile::FILTER_NORMALIZE => $normalizeThreshold
1706
+ ));
1707
+ }
1708
+
1709
+ /**
1710
+ * Add silence to the wav file.
1711
+ *
1712
+ * @param float $duration (Optional) How many seconds of silence. If negative, add to the beginning of the file. Defaults to 1s.
1713
+ */
1714
+ public function insertSilence($duration = 1.0)
1715
+ {
1716
+ $numSamples = (int)($this->getSampleRate() * abs($duration));
1717
+ $numChannels = $this->getNumChannels();
1718
+
1719
+ $data = str_repeat(self::packSample($this->getZeroAmplitude(), $this->getBitsPerSample()), $numSamples * $numChannels);
1720
+ if ($duration >= 0) {
1721
+ $this->_samples .= $data;
1722
+ } else {
1723
+ $this->_samples = $data . $this->_samples;
1724
+ }
1725
+
1726
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
1727
+
1728
+ return $this;
1729
+ }
1730
+
1731
+ /**
1732
+ * Degrade the quality of the wav file by introducing random noise.
1733
+ *
1734
+ * @param float quality (Optional) The quality relative to the amplitude. 1 = no noise, 0 = max. noise.
1735
+ */
1736
+ public function degrade($quality = 1.0)
1737
+ {
1738
+ return $this->filter(self::FILTER_DEGRADE, array(
1739
+ WavFile::FILTER_DEGRADE => $quality
1740
+ ));
1741
+ }
1742
+
1743
+ /**
1744
+ * Generate noise at the end of the wav for the specified duration and volume.
1745
+ *
1746
+ * @param float $duration (Optional) Number of seconds of noise to generate.
1747
+ * @param float $percent (Optional) The percentage of the maximum amplitude to use. 100 = full amplitude.
1748
+ */
1749
+ public function generateNoise($duration = 1.0, $percent = 100)
1750
+ {
1751
+ $numChannels = $this->getNumChannels();
1752
+ $numSamples = $this->getSampleRate() * $duration;
1753
+ $minAmp = $this->getMinAmplitude();
1754
+ $maxAmp = $this->getMaxAmplitude();
1755
+ $bitDepth = $this->getBitsPerSample();
1756
+
1757
+ for ($s = 0; $s < $numSamples; ++$s) {
1758
+ if ($bitDepth == 32) {
1759
+ $val = rand(-$percent * 10000, $percent * 10000) / 1000000;
1760
+ } else {
1761
+ $val = rand($minAmp, $maxAmp);
1762
+ $val = (int)($val * $percent / 100);
1763
+ }
1764
+
1765
+ $this->_samples .= str_repeat(self::packSample($val, $bitDepth), $numChannels);
1766
+ }
1767
+
1768
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
1769
+
1770
+ return $this;
1771
+ }
1772
+
1773
+ /**
1774
+ * Convert sample data to different bits per sample.
1775
+ *
1776
+ * @param int $bitsPerSample (Required) The new number of bits per sample;
1777
+ * @throws WavFileException
1778
+ */
1779
+ public function convertBitsPerSample($bitsPerSample) {
1780
+ if ($this->getBitsPerSample() == $bitsPerSample) {
1781
+ return $this;
1782
+ }
1783
+
1784
+ $tempWav = new WavFile($this->getNumChannels(), $this->getSampleRate(), $bitsPerSample);
1785
+ $tempWav->filter(
1786
+ array(self::FILTER_MIX => $this),
1787
+ 0,
1788
+ $this->getNumBlocks()
1789
+ );
1790
+
1791
+ $this->setSamples() // implicit setDataSize(), setSize(), setActualSize(), setNumBlocks()
1792
+ ->setBitsPerSample($bitsPerSample); // implicit setValidBitsPerSample(), setAudioFormat(), setAudioSubFormat(), setFmtChunkSize(), setFactChunkSize(), setSize(), setActualSize(), setDataOffset(), setByteRate(), setBlockAlign(), setNumBlocks()
1793
+ $this->_samples = $tempWav->_samples;
1794
+ $this->setDataSize(); // implicit setSize(), setActualSize(), setNumBlocks()
1795
+
1796
+ return $this;
1797
+ }
1798
+
1799
+
1800
+ /*%******************************************************************************************%*/
1801
+ // Miscellaneous methods
1802
+
1803
+ /**
1804
+ * Output information about the wav object.
1805
+ */
1806
+ public function displayInfo()
1807
+ {
1808
+ $s = "File Size: %u\n"
1809
+ ."Chunk Size: %u\n"
1810
+ ."fmt Subchunk Size: %u\n"
1811
+ ."Extended fmt Size: %u\n"
1812
+ ."fact Subchunk Size: %u\n"
1813
+ ."Data Offset: %u\n"
1814
+ ."Data Size: %u\n"
1815
+ ."Audio Format: %s\n"
1816
+ ."Audio SubFormat: %s\n"
1817
+ ."Channels: %u\n"
1818
+ ."Channel Mask: 0x%s\n"
1819
+ ."Sample Rate: %u\n"
1820
+ ."Bits Per Sample: %u\n"
1821
+ ."Valid Bits Per Sample: %u\n"
1822
+ ."Sample Block Size: %u\n"
1823
+ ."Number of Sample Blocks: %u\n"
1824
+ ."Byte Rate: %uBps\n";
1825
+
1826
+ $s = sprintf($s, $this->getActualSize(),
1827
+ $this->getChunkSize(),
1828
+ $this->getFmtChunkSize(),
1829
+ $this->getFmtExtendedSize(),
1830
+ $this->getFactChunkSize(),
1831
+ $this->getDataOffset(),
1832
+ $this->getDataSize(),
1833
+ $this->getAudioFormat() == self::WAVE_FORMAT_PCM ? 'PCM' : ($this->getAudioFormat() == self::WAVE_FORMAT_IEEE_FLOAT ? 'IEEE FLOAT' : 'EXTENSIBLE'),
1834
+ $this->getAudioSubFormat() == self::WAVE_SUBFORMAT_PCM ? 'PCM' : 'IEEE FLOAT',
1835
+ $this->getNumChannels(),
1836
+ dechex($this->getChannelMask()),
1837
+ $this->getSampleRate(),
1838
+ $this->getBitsPerSample(),
1839
+ $this->getValidBitsPerSample(),
1840
+ $this->getBlockAlign(),
1841
+ $this->getNumBlocks(),
1842
+ $this->getByteRate());
1843
+
1844
+ if (php_sapi_name() == 'cli') {
1845
+ return $s;
1846
+ } else {
1847
+ return nl2br($s);
1848
+ }
1849
+ }
1850
+ }
1851
+
1852
+
1853
+ /*%******************************************************************************************%*/
1854
+ // Exceptions
1855
+
1856
+ /**
1857
+ * WavFileException indicates an illegal state or argument in this class.
1858
+ */
1859
+ class WavFileException extends Exception {}
1860
+
1861
+ /**
1862
+ * WavFormatException indicates a malformed or unsupported wav file header.
1863
+ */
1864
+ class WavFormatException extends WavFileException {}
securimage/audio/0.wav DELETED
Binary file
securimage/audio/1.wav DELETED
Binary file
securimage/audio/2.wav DELETED
Binary file
securimage/audio/3.wav DELETED
Binary file
securimage/audio/4.wav DELETED
Binary file
securimage/audio/5.wav DELETED
Binary file
securimage/audio/6.wav DELETED
Binary file
securimage/audio/7.wav DELETED
Binary file
securimage/audio/8.wav DELETED
Binary file
securimage/audio/9.wav DELETED
Binary file
securimage/audio/A.wav DELETED
Binary file
securimage/audio/B.wav DELETED
Binary file
securimage/audio/C.wav DELETED
Binary file
securimage/audio/D.wav DELETED
Binary file
securimage/audio/E.wav DELETED
Binary file
securimage/audio/F.wav DELETED
Binary file
securimage/audio/G.wav DELETED
Binary file
securimage/audio/H.wav DELETED
Binary file
securimage/audio/I.wav DELETED
Binary file
securimage/audio/J.wav DELETED
Binary file
securimage/audio/K.wav DELETED
Binary file
securimage/audio/L.wav DELETED
Binary file
securimage/audio/M.wav DELETED
Binary file
securimage/audio/N.wav DELETED
Binary file
securimage/audio/O.wav DELETED
Binary file
securimage/audio/P.wav DELETED
Binary file
securimage/audio/Q.wav DELETED
Binary file
securimage/audio/R.wav DELETED
Binary file
securimage/audio/S.wav DELETED
Binary file
securimage/audio/T.wav DELETED
Binary file
securimage/audio/U.wav DELETED
Binary file
securimage/audio/V.wav DELETED
Binary file
securimage/audio/W.wav DELETED
Binary file
securimage/audio/X.wav DELETED
Binary file
securimage/audio/Y.wav DELETED
Binary file
securimage/audio/Z.wav DELETED
Binary file
securimage/audio/en/0.wav ADDED
Binary file
securimage/audio/en/1.wav ADDED
Binary file
securimage/audio/en/10.wav ADDED
Binary file
securimage/audio/en/11.wav ADDED
Binary file
securimage/audio/en/12.wav ADDED
Binary file
securimage/audio/en/13.wav ADDED
Binary file
securimage/audio/en/14.wav ADDED
Binary file
securimage/audio/en/15.wav ADDED
Binary file
securimage/audio/en/16.wav ADDED
Binary file
securimage/audio/en/17.wav ADDED
Binary file
securimage/audio/en/18.wav ADDED
Binary file
securimage/audio/en/19.wav ADDED
Binary file
securimage/audio/en/2.wav ADDED
Binary file
securimage/audio/en/20.wav ADDED
Binary file
securimage/audio/en/3.wav ADDED
Binary file
securimage/audio/en/4.wav ADDED
Binary file
securimage/audio/en/5.wav ADDED
Binary file
securimage/audio/en/6.wav ADDED
Binary file
securimage/audio/en/7.wav ADDED
Binary file
securimage/audio/en/8.wav ADDED
Binary file
securimage/audio/en/9.wav ADDED
Binary file
securimage/audio/en/A.wav ADDED
Binary file
securimage/audio/en/B.wav ADDED
Binary file
securimage/audio/en/C.wav ADDED
Binary file
securimage/audio/en/D.wav ADDED
Binary file
securimage/audio/en/E.wav ADDED
Binary file
securimage/audio/en/F.wav ADDED
Binary file
securimage/audio/en/G.wav ADDED
Binary file
securimage/audio/en/H.wav ADDED
Binary file
securimage/audio/en/I.wav ADDED
Binary file
securimage/audio/en/J.wav ADDED
Binary file
securimage/audio/en/K.wav ADDED
Binary file
securimage/audio/en/L.wav ADDED
Binary file
securimage/audio/en/M.wav ADDED
Binary file
securimage/audio/en/MINUS.wav ADDED
Binary file
securimage/audio/en/N.wav ADDED
Binary file
securimage/audio/en/O.wav ADDED
Binary file
securimage/audio/en/P.wav ADDED
Binary file
securimage/audio/en/PLUS.wav ADDED
Binary file
securimage/audio/en/Q.wav ADDED
Binary file
securimage/audio/en/R.wav ADDED
Binary file
securimage/audio/en/S.wav ADDED
Binary file
securimage/audio/en/T.wav ADDED
Binary file
securimage/audio/en/TIMES.wav ADDED
Binary file
securimage/audio/en/U.wav ADDED
Binary file
securimage/audio/en/V.wav ADDED
Binary file
securimage/audio/en/W.wav ADDED
Binary file
securimage/audio/en/X.wav ADDED
Binary file
securimage/audio/en/Y.wav ADDED
Binary file
securimage/audio/en/Z.wav ADDED
Binary file
securimage/audio/{error.wav → en/error.wav} RENAMED
File without changes
securimage/captcha.html CHANGED
@@ -2,12 +2,12 @@
2
 
3
  <p>
4
  <img id="siimage" style="border: 1px solid #000; margin-right: 15px" src="./securimage_show.php?sid=<?php echo md5(uniqid()) ?>" alt="CAPTCHA Image" align="left">
5
- <object type="application/x-shockwave-flash" data="./securimage_play.swf?audio_file=./securimage_play.php&amp;bgColor1=#fff&amp;bgColor2=#fff&amp;iconColor=#777&amp;borderWidth=1&amp;borderColor=#000" height="32" width="32">
6
- <param name="movie" value="./securimage_play.swf?audio_file=./securimage_play.php&amp;bgColor1=#fff&amp;bgColor2=#fff&amp;iconColor=#777&amp;borderWidth=1&amp;borderColor=#000">
7
  </object>
8
  &nbsp;
9
  <a tabindex="-1" style="border-style: none;" href="#" title="Refresh Image" onclick="document.getElementById('siimage').src = './securimage_show.php?sid=' + Math.random(); this.blur(); return false"><img src="./images/refresh.png" alt="Reload Image" onclick="this.blur()" align="bottom" border="0"></a><br />
10
  <strong>Enter Code*:</strong><br />
11
- <input type="text" name="ct_captcha" size="12" maxlength="8" />
12
  </p>
13
 
2
 
3
  <p>
4
  <img id="siimage" style="border: 1px solid #000; margin-right: 15px" src="./securimage_show.php?sid=<?php echo md5(uniqid()) ?>" alt="CAPTCHA Image" align="left">
5
+ <object type="application/x-shockwave-flash" data="./securimage_play.swf?bgcol=#ffffff&amp;icon_file=./images/audio_icon.png&amp;audio_file=./securimage_play.php" height="32" width="32">
6
+ <param name="movie" value="./securimage_play.swf?bgcol=#ffffff&amp;icon_file=./images/audio_icon.png&amp;audio_file=./securimage_play.php" />
7
  </object>
8
  &nbsp;
9
  <a tabindex="-1" style="border-style: none;" href="#" title="Refresh Image" onclick="document.getElementById('siimage').src = './securimage_show.php?sid=' + Math.random(); this.blur(); return false"><img src="./images/refresh.png" alt="Reload Image" onclick="this.blur()" align="bottom" border="0"></a><br />
10
  <strong>Enter Code*:</strong><br />
11
+ <input type="text" name="ct_captcha" size="12" maxlength="16" />
12
  </p>
13
 
securimage/database/securimage.sq3 ADDED
Binary file
securimage/database/securimage.sqlite DELETED
Binary file
securimage/example_form.ajax.php CHANGED
@@ -13,9 +13,10 @@ $GLOBALS['DEBUG_MODE'] = 1;
13
  process_si_contact_form();
14
 
15
  ?>
16
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
17
- <html>
18
  <head>
 
19
  <title>Securimage Example Form</title>
20
  <style type="text/css">
21
  <!--
@@ -25,15 +26,15 @@ process_si_contact_form();
25
  .note { font-size: 18px; }
26
  -->
27
  </style>
28
-
29
- <script src="https://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js"></script>
30
-
31
  <script type="text/javascript">
32
  function reloadCaptcha()
33
  {
34
  document.getElementById('siimage').src = './securimage_show.php?sid=' + Math.random();
35
  }
36
-
37
  function processForm()
38
  {
39
  new Ajax.Request('<?php echo $_SERVER['PHP_SELF'] ?>', {
@@ -96,23 +97,23 @@ process_si_contact_form();
96
 
97
  <p>
98
  <strong>Message*:</strong><br />
99
- <textarea name="ct_message" style="width: 450px; height: 200px"></textarea>
100
  </p>
101
 
102
  <p>
103
- <img id="siimage" style="border: 1px solid #000; margin-right: 15px" src="./securimage_show.php?sid=<?php echo md5(uniqid()) ?>" alt="CAPTCHA Image" align="left">
104
- <object type="application/x-shockwave-flash" data="./securimage_play.swf?audio_file=./securimage_play.php&amp;bgColor1=#fff&amp;bgColor2=#fff&amp;iconColor=#777&amp;borderWidth=1&amp;borderColor=#000" height="32" width="32">
105
- <param name="movie" value="./securimage_play.swf?audio_file=./securimage_play.php&amp;bgColor1=#fff&amp;bgColor2=#fff&amp;iconColor=#777&amp;borderWidth=1&amp;borderColor=#000">
106
  </object>
107
  &nbsp;
108
- <a tabindex="-1" style="border-style: none;" href="#" title="Refresh Image" onclick="reloadCaptcha(); this.blur(); return false"><img src="./images/refresh.png" alt="Reload Image" onclick="this.blur()" align="bottom" border="0"></a><br />
109
  <strong>Enter Code*:</strong><br />
110
  <input type="text" name="ct_captcha" size="12" maxlength="8" />
111
  </p>
112
 
113
  <p>
114
  <br />
115
- <input type="submit" value="Submit Message">
116
  </p>
117
 
118
  </form>
@@ -128,7 +129,7 @@ function process_si_contact_form()
128
  {
129
  if ($_SERVER['REQUEST_METHOD'] == 'POST' && @$_POST['do'] == 'contact') {
130
  // if the form has been submitted
131
-
132
  foreach($_POST as $key => $value) {
133
  if (!is_array($key)) {
134
  // sanitize the input data
@@ -148,7 +149,7 @@ function process_si_contact_form()
148
 
149
  if (isset($GLOBALS['DEBUG_MODE']) && $GLOBALS['DEBUG_MODE'] == false) {
150
  // only check for errors if the form is not in debug mode
151
-
152
  if (strlen($name) < 3) {
153
  // name too short, add error
154
  $errors['name_error'] = 'Your name is required';
@@ -173,7 +174,7 @@ function process_si_contact_form()
173
  if (sizeof($errors) == 0) {
174
  require_once dirname(__FILE__) . '/securimage.php';
175
  $securimage = new Securimage();
176
-
177
  if ($securimage->check($captcha) == false) {
178
  $errors['captcha_error'] = 'Incorrect security code entered';
179
  }
@@ -196,7 +197,7 @@ function process_si_contact_form()
196
  // send the message with mail()
197
  mail($GLOBALS['ct_recipient'], $GLOBALS['ct_msg_subject'], $message, "From: {$GLOBALS['ct_recipient']}\r\nReply-To: {$email}\r\nContent-type: text/html; charset=ISO-8859-1\r\nMIME-Version: 1.0");
198
  }
199
-
200
  $return = array('error' => 0, 'message' => 'OK');
201
  die(json_encode($return));
202
  } else {
13
  process_si_contact_form();
14
 
15
  ?>
16
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
17
+ <html xmlns="http://www.w3.org/1999/xhtml">
18
  <head>
19
+ <meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
20
  <title>Securimage Example Form</title>
21
  <style type="text/css">
22
  <!--
26
  .note { font-size: 18px; }
27
  -->
28
  </style>
29
+
30
+ <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js"></script>
31
+
32
  <script type="text/javascript">
33
  function reloadCaptcha()
34
  {
35
  document.getElementById('siimage').src = './securimage_show.php?sid=' + Math.random();
36
  }
37
+
38
  function processForm()
39
  {
40
  new Ajax.Request('<?php echo $_SERVER['PHP_SELF'] ?>', {
97
 
98
  <p>
99
  <strong>Message*:</strong><br />
100
+ <textarea name="ct_message" rows="12" cols="60"></textarea>
101
  </p>
102
 
103
  <p>
104
+ <img id="siimage" style="border: 1px solid #000; margin-right: 15px" src="./securimage_show.php?sid=<?php echo md5(uniqid()) ?>" alt="CAPTCHA Image" align="left" />
105
+ <object type="application/x-shockwave-flash" data="./securimage_play.swf?bgcol=#ffffff&amp;icon_file=./images/audio_icon.png&amp;audio_file=./securimage_play.php" height="32" width="32">
106
+ <param name="movie" value="./securimage_play.swf?bgcol=#ffffff&amp;icon_file=./images/audio_icon.png&amp;audio_file=./securimage_play.php" />
107
  </object>
108
  &nbsp;
109
+ <a tabindex="-1" style="border-style: none;" href="#" title="Refresh Image" onclick="document.getElementById('siimage').src = './securimage_show.php?sid=' + Math.random(); this.blur(); return false"><img src="./images/refresh.png" alt="Reload Image" height="32" width="32" onclick="this.blur()" align="bottom" border="0" /></a><br />
110
  <strong>Enter Code*:</strong><br />
111
  <input type="text" name="ct_captcha" size="12" maxlength="8" />
112
  </p>
113
 
114
  <p>
115
  <br />
116
+ <input type="submit" value="Submit Message" />
117
  </p>
118
 
119
  </form>
129
  {
130
  if ($_SERVER['REQUEST_METHOD'] == 'POST' && @$_POST['do'] == 'contact') {
131
  // if the form has been submitted
132
+
133
  foreach($_POST as $key => $value) {
134
  if (!is_array($key)) {
135
  // sanitize the input data
149
 
150
  if (isset($GLOBALS['DEBUG_MODE']) && $GLOBALS['DEBUG_MODE'] == false) {
151
  // only check for errors if the form is not in debug mode
152
+
153
  if (strlen($name) < 3) {
154
  // name too short, add error
155
  $errors['name_error'] = 'Your name is required';
174
  if (sizeof($errors) == 0) {
175
  require_once dirname(__FILE__) . '/securimage.php';
176
  $securimage = new Securimage();
177
+
178
  if ($securimage->check($captcha) == false) {
179
  $errors['captcha_error'] = 'Incorrect security code entered';
180
  }
197
  // send the message with mail()
198
  mail($GLOBALS['ct_recipient'], $GLOBALS['ct_msg_subject'], $message, "From: {$GLOBALS['ct_recipient']}\r\nReply-To: {$email}\r\nContent-type: text/html; charset=ISO-8859-1\r\nMIME-Version: 1.0");
199
  }
200
+
201
  $return = array('error' => 0, 'message' => 'OK');
202
  die(json_encode($return));
203
  } else {
securimage/example_form.php CHANGED
@@ -5,15 +5,19 @@ $GLOBALS['DEBUG_MODE'] = 1;
5
  // CHANGE TO 0 TO TURN OFF DEBUG MODE
6
  // IN DEBUG MODE, ONLY THE CAPTCHA CODE IS VALIDATED, AND NO EMAIL IS SENT
7
 
 
 
 
8
  ?>
9
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
10
- <html>
11
  <head>
 
12
  <title>Securimage Example Form</title>
13
  <style type="text/css">
14
  <!--
15
  .error { color: #f00; font-weight: bold; font-size: 1.2em; }
16
- .success { color: #00f; font-weight; bold; font-size: 1.2em; }
17
  fieldset { width: 90%; }
18
  legend { font-size: 24px; }
19
  .note { font-size: 18px;
@@ -32,9 +36,6 @@ $GLOBALS['DEBUG_MODE'] = 1;
32
 
33
  <?php
34
 
35
- $GLOBALS['ct_recipient'] = 'YOU@EXAMPLE.COm'; // Change to your email address!
36
- $GLOBALS['ct_msg_subject'] = 'Securimage Test Contact Form';
37
-
38
  process_si_contact_form(); // Process the form, if it was submitted
39
 
40
  if (isset($_SESSION['ctform']['error']) && $_SESSION['ctform']['error'] == true): /* The last form submission had 1 or more errors */ ?>
@@ -63,24 +64,24 @@ if (isset($_SESSION['ctform']['error']) && $_SESSION['ctform']['error'] == true
63
 
64
  <p>
65
  <strong>Message*:</strong>&nbsp; &nbsp;<?php echo @$_SESSION['ctform']['message_error'] ?><br />
66
- <textarea name="ct_message" style="width: 450px; height: 200px"><?php echo htmlspecialchars(@$_SESSION['ctform']['ct_message']) ?></textarea>
67
  </p>
68
 
69
  <p>
70
- <img id="siimage" style="border: 1px solid #000; margin-right: 15px" src="./securimage_show.php?sid=<?php echo md5(uniqid()) ?>" alt="CAPTCHA Image" align="left">
71
- <object type="application/x-shockwave-flash" data="./securimage_play.swf?audio_file=./securimage_play.php&amp;bgColor1=#fff&amp;bgColor2=#fff&amp;iconColor=#777&amp;borderWidth=1&amp;borderColor=#000" height="32" width="32">
72
- <param name="movie" value="./securimage_play.swf?audio_file=./securimage_play.php&amp;bgColor1=#fff&amp;bgColor2=#fff&amp;iconColor=#777&amp;borderWidth=1&amp;borderColor=#000">
73
  </object>
74
  &nbsp;
75
- <a tabindex="-1" style="border-style: none;" href="#" title="Refresh Image" onclick="document.getElementById('siimage').src = './securimage_show.php?sid=' + Math.random(); this.blur(); return false"><img src="./images/refresh.png" alt="Reload Image" onclick="this.blur()" align="bottom" border="0"></a><br />
76
  <strong>Enter Code*:</strong><br />
77
  <?php echo @$_SESSION['ctform']['captcha_error'] ?>
78
- <input type="text" name="ct_captcha" size="12" maxlength="8" />
79
  </p>
80
 
81
  <p>
82
  <br />
83
- <input type="submit" value="Submit Message">
84
  </p>
85
 
86
  </form>
@@ -98,7 +99,7 @@ function process_si_contact_form()
98
 
99
  if ($_SERVER['REQUEST_METHOD'] == 'POST' && @$_POST['do'] == 'contact') {
100
  // if the form has been submitted
101
-
102
  foreach($_POST as $key => $value) {
103
  if (!is_array($key)) {
104
  // sanitize the input data
@@ -118,7 +119,7 @@ function process_si_contact_form()
118
 
119
  if (isset($GLOBALS['DEBUG_MODE']) && $GLOBALS['DEBUG_MODE'] == false) {
120
  // only check for errors if the form is not in debug mode
121
-
122
  if (strlen($name) < 3) {
123
  // name too short, add error
124
  $errors['name_error'] = 'Your name is required';
@@ -143,7 +144,7 @@ function process_si_contact_form()
143
  if (sizeof($errors) == 0) {
144
  require_once dirname(__FILE__) . '/securimage.php';
145
  $securimage = new Securimage();
146
-
147
  if ($securimage->check($captcha) == false) {
148
  $errors['captcha_error'] = 'Incorrect security code entered<br />';
149
  }
5
  // CHANGE TO 0 TO TURN OFF DEBUG MODE
6
  // IN DEBUG MODE, ONLY THE CAPTCHA CODE IS VALIDATED, AND NO EMAIL IS SENT
7
 
8
+ $GLOBALS['ct_recipient'] = 'YOU@EXAMPLE.COM'; // Change to your email address!
9
+ $GLOBALS['ct_msg_subject'] = 'Securimage Test Contact Form';
10
+
11
  ?>
12
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
13
+ <html xmlns="http://www.w3.org/1999/xhtml">
14
  <head>
15
+ <meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
16
  <title>Securimage Example Form</title>
17
  <style type="text/css">
18
  <!--
19
  .error { color: #f00; font-weight: bold; font-size: 1.2em; }
20
+ .success { color: #00f; font-weight: bold; font-size: 1.2em; }
21
  fieldset { width: 90%; }
22
  legend { font-size: 24px; }
23
  .note { font-size: 18px;
36
 
37
  <?php
38
 
 
 
 
39
  process_si_contact_form(); // Process the form, if it was submitted
40
 
41
  if (isset($_SESSION['ctform']['error']) && $_SESSION['ctform']['error'] == true): /* The last form submission had 1 or more errors */ ?>
64
 
65
  <p>
66
  <strong>Message*:</strong>&nbsp; &nbsp;<?php echo @$_SESSION['ctform']['message_error'] ?><br />
67
+ <textarea name="ct_message" rows="12" cols="60"><?php echo htmlspecialchars(@$_SESSION['ctform']['ct_message']) ?></textarea>
68
  </p>
69
 
70
  <p>
71
+ <img id="siimage" style="border: 1px solid #000; margin-right: 15px" src="./securimage_show.php?sid=<?php echo md5(uniqid()) ?>" alt="CAPTCHA Image" align="left" />
72
+ <object type="application/x-shockwave-flash" data="./securimage_play.swf?bgcol=#ffffff&amp;icon_file=./images/audio_icon.png&amp;audio_file=./securimage_play.php" height="32" width="32">
73
+ <param name="movie" value="./securimage_play.swf?bgcol=#ffffff&amp;icon_file=./images/audio_icon.png&amp;audio_file=./securimage_play.php" />
74
  </object>
75
  &nbsp;
76
+ <a tabindex="-1" style="border-style: none;" href="#" title="Refresh Image" onclick="document.getElementById('siimage').src = './securimage_show.php?sid=' + Math.random(); this.blur(); return false"><img src="./images/refresh.png" alt="Reload Image" height="32" width="32" onclick="this.blur()" align="bottom" border="0" /></a><br />
77
  <strong>Enter Code*:</strong><br />
78
  <?php echo @$_SESSION['ctform']['captcha_error'] ?>
79
+ <input type="text" name="ct_captcha" size="12" maxlength="16" />
80
  </p>
81
 
82
  <p>
83
  <br />
84
+ <input type="submit" value="Submit Message" />
85
  </p>
86
 
87
  </form>
99
 
100
  if ($_SERVER['REQUEST_METHOD'] == 'POST' && @$_POST['do'] == 'contact') {
101
  // if the form has been submitted
102
+
103
  foreach($_POST as $key => $value) {
104
  if (!is_array($key)) {
105
  // sanitize the input data
119
 
120
  if (isset($GLOBALS['DEBUG_MODE']) && $GLOBALS['DEBUG_MODE'] == false) {
121
  // only check for errors if the form is not in debug mode
122
+
123
  if (strlen($name) < 3) {
124
  // name too short, add error
125
  $errors['name_error'] = 'Your name is required';
144
  if (sizeof($errors) == 0) {
145
  require_once dirname(__FILE__) . '/securimage.php';
146
  $securimage = new Securimage();
147
+
148
  if ($securimage->check($captcha) == false) {
149
  $errors['captcha_error'] = 'Incorrect security code entered<br />';
150
  }
securimage/images/audio_icon.gif DELETED
Binary file
securimage/images/audio_icon.png ADDED
Binary file
securimage/images/refresh.gif DELETED
Binary file
securimage/images/refresh.png CHANGED
Binary file
securimage/securimage.php CHANGED
@@ -6,18 +6,18 @@
6
  * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br />
7
  * File: securimage.php<br />
8
  *
9
- * Copyright (c) 2011, Drew Phillips
10
  * All rights reserved.
11
- *
12
  * Redistribution and use in source and binary forms, with or without modification,
13
  * are permitted provided that the following conditions are met:
14
- *
15
  * - Redistributions of source code must retain the above copyright notice,
16
  * this list of conditions and the following disclaimer.
17
  * - Redistributions in binary form must reproduce the above copyright notice,
18
  * this list of conditions and the following disclaimer in the documentation
19
  * and/or other materials provided with the distribution.
20
- *
21
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -39,19 +39,59 @@
39
  * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
40
  * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
41
  * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
42
- * @copyright 2011 Drew Phillips
43
  * @author Drew Phillips <drew@drew-phillips.com>
44
- * @version 3.0.1 (January 2012)
45
  * @package Securimage
46
  *
47
  */
48
 
49
  /**
50
  ChangeLog
51
-
52
- 3.1
53
- - Bugfix: removed use of deprecated variable in addSignature method that would cause errors with display_errors on
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  3.0
56
  - Rewrite class using PHP5 OOP
57
  - Remove support for GD fonts, require FreeType
@@ -83,7 +123,7 @@
83
  - Flash button to stream mp3 audio (Douglas Walsh www.douglaswalsh.net)
84
  - Audio output is mp3 format by default
85
  - Change font to AlteHaasGrotesk by yann le coroller
86
- - Some code cleanup
87
 
88
  1.0.4 (unreleased)
89
  - Ability to output audible codes in mp3 format to stream from flash
@@ -113,7 +153,7 @@
113
  /**
114
  * Securimage CAPTCHA Class.
115
  *
116
- * @version 3.0
117
  * @package Securimage
118
  * @subpackage classes
119
  * @author Drew Phillips <drew@drew-phillips.com>
@@ -124,7 +164,7 @@ class Securimage
124
  // All of the public variables below are securimage options
125
  // They can be passed as an array to the Securimage constructor, set below,
126
  // or set from securimage_show.php and securimage_play.php
127
-
128
  /**
129
  * Renders captcha as a JPEG image
130
  * @var int
@@ -140,7 +180,7 @@ class Securimage
140
  * @var int
141
  */
142
  const SI_IMAGE_GIF = 3;
143
-
144
  /**
145
  * Create a normal alphanumeric captcha
146
  * @var int
@@ -151,6 +191,35 @@ class Securimage
151
  * @var int
152
  */
153
  const SI_CAPTCHA_MATHEMATIC = 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
  /**
156
  * The width of the captcha image
@@ -185,21 +254,21 @@ class Securimage
185
  public $line_color = '#707070';
186
  /**
187
  * The color of the noise that is drawn
188
- * @var Securimage_Color
189
  */
190
  public $noise_color = '#707070';
191
-
192
  /**
193
  * How transparent to make the text 0 = completely opaque, 100 = invisible
194
  * @var int
195
  */
196
- public $text_transparency_percentage = 50;
197
  /**
198
  * Whether or not to draw the text transparently, true = use transparency, false = no transparency
199
  * @var bool
200
  */
201
- public $use_transparent_text = false;
202
-
203
  /**
204
  * The length of the captcha code
205
  * @var int
@@ -220,14 +289,14 @@ class Securimage
220
  * @var unknown_type
221
  */
222
  public $expiry_time = 900;
223
-
224
  /**
225
  * The session name securimage should use, only set this if your application uses a custom session name
226
  * It is recommended to set this value below so it is used by all securimage scripts
227
  * @var string
228
  */
229
  public $session_name = null;
230
-
231
  /**
232
  * true to use the wordlist file, false to generate random captcha codes
233
  * @var bool
@@ -238,18 +307,18 @@ class Securimage
238
  * The level of distortion, 0.75 = normal, 1.0 = very high distortion
239
  * @var double
240
  */
241
- public $perturbation = 0.75;
242
  /**
243
  * How many lines to draw over the captcha code to increase security
244
  * @var int
245
  */
246
- public $num_lines = 8;
247
  /**
248
  * The level of noise (random dots) to place on the image, 0-10
249
  * @var int
250
  */
251
- public $noise_level = 0;
252
-
253
  /**
254
  * The signature text to draw on the bottom corner of the image
255
  * @var string
@@ -265,20 +334,93 @@ class Securimage
265
  * @var string
266
  */
267
  public $signature_font;
268
-
269
  /**
 
270
  * Use an SQLite database to store data (for users that do not support cookies)
271
  * @var bool
 
 
272
  */
273
  public $use_sqlite_db = false;
274
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  /**
276
  * The type of captcha to create, either alphanumeric, or a math problem<br />
277
  * Securimage::SI_CAPTCHA_STRING or Securimage::SI_CAPTCHA_MATHEMATIC
278
  * @var int
279
  */
280
- public $captcha_type = self::SI_CAPTCHA_STRING;
281
-
282
  /**
283
  * The captcha namespace, use this if you have multiple forms on a single page, blank if you do not use multiple forms on one page
284
  * @var string
@@ -286,7 +428,7 @@ class Securimage
286
  * <?php
287
  * // in securimage_show.php (create one show script for each form)
288
  * $img->namespace = 'contact_form';
289
- *
290
  * // in form validator
291
  * $img->namespace = 'contact_form';
292
  * if ($img->check($code) == true) {
@@ -295,7 +437,7 @@ class Securimage
295
  * </code>
296
  */
297
  public $namespace;
298
-
299
  /**
300
  * The font file to use to draw the captcha code, leave blank for default font AHGBold.ttf
301
  * @var string
@@ -313,6 +455,7 @@ class Securimage
313
  public $background_directory;
314
  /**
315
  * The path to the SQLite database file to use, if $use_sqlite_database = true, should be chmod 666
 
316
  * @var string
317
  */
318
  public $sqlite_database;
@@ -320,31 +463,150 @@ class Securimage
320
  * The path to the securimage audio directory, can be set in securimage_play.php
321
  * @var string
322
  * <code>
323
- * $img->audio_path = '/home/yoursite/public_html/securimage/audio/';
324
  * </code>
325
  */
326
  public $audio_path;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
-
329
-
330
  protected $im;
331
  protected $tmpimg;
332
  protected $bgimg;
333
  protected $iscale = 5;
334
-
335
- protected $securimage_path = null;
336
-
 
 
 
 
 
337
  protected $code;
 
 
 
 
 
 
338
  protected $code_display;
339
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  protected $captcha_code;
341
- protected $sqlite_handle;
342
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  protected $gdbgcolor;
344
  protected $gdtextcolor;
345
  protected $gdlinecolor;
346
  protected $gdsignaturecolor;
347
-
348
  /**
349
  * Create a new securimage object, pass options to set in the constructor.<br />
350
  * This can be used to display a captcha, play an audible captcha, or validate an entry
@@ -357,17 +619,24 @@ class Securimage
357
  * 'noise_level' => 3,
358
  * 'font_file' => Securimage::getPath() . '/custom.ttf'
359
  * );
360
- *
361
  * $img = new Securimage($options);
362
  * </code>
363
  */
364
  public function __construct($options = array())
365
  {
366
  $this->securimage_path = dirname(__FILE__);
367
-
368
  if (is_array($options) && sizeof($options) > 0) {
369
  foreach($options as $prop => $val) {
370
- $this->$prop = $val;
 
 
 
 
 
 
 
371
  }
372
  }
373
 
@@ -377,45 +646,71 @@ class Securimage
377
  $this->noise_color = $this->initColor($this->noise_color, '#616161');
378
  $this->signature_color = $this->initColor($this->signature_color, '#616161');
379
 
380
- if ($this->ttf_file == null) {
381
  $this->ttf_file = $this->securimage_path . '/AHGBold.ttf';
382
  }
383
-
384
  $this->signature_font = $this->ttf_file;
385
-
386
- if ($this->wordlist_file == null) {
387
  $this->wordlist_file = $this->securimage_path . '/words/words.txt';
388
  }
389
-
390
- if ($this->sqlite_database == null) {
391
- $this->sqlite_database = $this->securimage_path . '/database/securimage.sqlite';
392
  }
393
-
394
- if ($this->audio_path == null) {
395
- $this->audio_path = $this->securimage_path . '/audio/';
396
  }
397
-
398
- if ($this->code_length == null || $this->code_length < 1) {
 
 
 
 
 
 
 
 
 
 
 
 
399
  $this->code_length = 6;
400
  }
401
-
402
- if ($this->perturbation == null || !is_numeric($this->perturbation)) {
403
  $this->perturbation = 0.75;
404
  }
405
-
406
- if ($this->namespace == null || !is_string($this->namespace)) {
407
  $this->namespace = 'default';
408
  }
409
 
410
- // Initialize session or attach to existing
411
- if ( session_id() == '' ) { // no session has been started yet, which is needed for validation
412
- if ($this->session_name != null && trim($this->session_name) != '') {
413
- session_name(trim($this->session_name)); // set session name if provided
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
  }
415
- session_start();
416
  }
417
  }
418
-
419
  /**
420
  * Return the absolute path to the Securimage directory
421
  * @return string The path to the securimage base directory
@@ -424,29 +719,99 @@ class Securimage
424
  {
425
  return dirname(__FILE__);
426
  }
427
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
  /**
429
  * Used to serve a captcha image to the browser
430
  * @param string $background_image The path to the background image to use
431
- * <code>
432
  * $img = new Securimage();
433
  * $img->code_length = 6;
434
  * $img->num_lines = 5;
435
  * $img->noise_level = 5;
436
- *
437
  * $img->show(); // sends the image to browser
438
  * exit;
439
  * </code>
440
  */
441
  public function show($background_image = '')
442
  {
 
 
443
  if($background_image != '' && is_readable($background_image)) {
444
  $this->bgimg = $background_image;
445
  }
446
 
447
  $this->doImage();
448
  }
449
-
450
  /**
451
  * Check a submitted code against the stored value
452
  * @param string $code The captcha code to check
@@ -466,10 +831,10 @@ class Securimage
466
  $this->validate();
467
  return $this->correct_code;
468
  }
469
-
470
  /**
471
  * Output a wav file of the captcha code to the browser
472
- *
473
  * <code>
474
  * $img = new Securimage();
475
  * $img->outputAudioFile(); // outputs a wav file to the browser
@@ -478,22 +843,98 @@ class Securimage
478
  */
479
  public function outputAudioFile()
480
  {
481
- $ext = 'wav'; // force wav - mp3 is insecure
482
-
483
- header("Content-Disposition: attachment; filename=\"securimage_audio.{$ext}\"");
484
- header('Cache-Control: no-store, no-cache, must-revalidate');
485
- header('Expires: Sun, 1 Jan 2000 12:00:00 GMT');
486
- header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
487
- header('Content-type: audio/x-wav');
488
-
489
- $audio = $this->getAudibleCode($ext);
490
 
491
- header('Content-Length: ' . strlen($audio));
492
 
493
- echo $audio;
494
- exit;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
495
  }
496
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
  /**
498
  * The main image drawing routing, responsible for constructing the entire image and serving it
499
  */
@@ -504,23 +945,52 @@ class Securimage
504
  } else {
505
  $imagecreate = 'imagecreate';
506
  }
507
-
508
  $this->im = $imagecreate($this->image_width, $this->image_height);
509
  $this->tmpimg = $imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale);
510
-
511
  $this->allocateColors();
512
  imagepalettecopy($this->tmpimg, $this->im);
513
 
514
  $this->setBackground();
515
 
516
- $this->createCode();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
518
  if ($this->noise_level > 0) {
519
  $this->drawNoise();
520
  }
521
-
522
  $this->drawWord();
523
-
524
  if ($this->perturbation > 0 && is_readable($this->ttf_file)) {
525
  $this->distortedCopy();
526
  }
@@ -535,7 +1005,7 @@ class Securimage
535
 
536
  $this->output();
537
  }
538
-
539
  /**
540
  * Allocate the colors to be used for the image
541
  */
@@ -546,9 +1016,9 @@ class Securimage
546
  $this->image_bg_color->r,
547
  $this->image_bg_color->g,
548
  $this->image_bg_color->b);
549
-
550
  $alpha = intval($this->text_transparency_percentage / 100 * 127);
551
-
552
  if ($this->use_transparent_text == true) {
553
  $this->gdtextcolor = imagecolorallocatealpha($this->im,
554
  $this->text_color->r,
@@ -579,14 +1049,14 @@ class Securimage
579
  $this->noise_color->g,
580
  $this->noise_color->b);
581
  }
582
-
583
  $this->gdsignaturecolor = imagecolorallocate($this->im,
584
  $this->signature_color->r,
585
  $this->signature_color->g,
586
  $this->signature_color->b);
587
 
588
  }
589
-
590
  /**
591
  * The the background color, or background image to be used
592
  */
@@ -599,9 +1069,9 @@ class Securimage
599
  imagefilledrectangle($this->tmpimg, 0, 0,
600
  $this->image_width * $this->iscale, $this->image_height * $this->iscale,
601
  $this->gdbgcolor);
602
-
603
  if ($this->bgimg == '') {
604
- if ($this->background_directory != null &&
605
  is_dir($this->background_directory) &&
606
  is_readable($this->background_directory))
607
  {
@@ -611,13 +1081,13 @@ class Securimage
611
  }
612
  }
613
  }
614
-
615
  if ($this->bgimg == '') {
616
  return;
617
  }
618
 
619
  $dat = @getimagesize($this->bgimg);
620
- if($dat == false) {
621
  return;
622
  }
623
 
@@ -634,7 +1104,7 @@ class Securimage
634
  $this->image_width, $this->image_height,
635
  imagesx($newim), imagesy($newim));
636
  }
637
-
638
  /**
639
  * Scan the directory for a background image to use
640
  */
@@ -650,39 +1120,47 @@ class Securimage
650
  closedir($dh);
651
 
652
  if (sizeof($images) > 0) {
653
- return rtrim($this->background_directory, '/') . '/' . $images[rand(0, sizeof($images)-1)];
654
  }
655
  }
656
 
657
  return false;
658
  }
659
-
660
  /**
661
  * Generates the code or math problem and saves the value to the session
662
  */
663
- protected function createCode()
664
  {
665
  $this->code = false;
666
 
667
  switch($this->captcha_type) {
668
  case self::SI_CAPTCHA_MATHEMATIC:
669
  {
670
- $signs = array('+', '-', 'x');
671
- $left = rand(1, 10);
672
- $right = rand(1, 5);
673
- $sign = $signs[rand(0, 2)];
674
-
675
- switch($sign) {
676
- case 'x': $c = $left * $right; break;
677
- case '-': $c = $left - $right; break;
678
- default: $c = $left + $right; break;
679
- }
680
-
 
 
681
  $this->code = $c;
682
  $this->code_display = "$left $sign $right";
683
  break;
684
  }
685
 
 
 
 
 
 
 
686
  default:
687
  {
688
  if ($this->use_wordlist && is_readable($this->wordlist_file)) {
@@ -692,15 +1170,15 @@ class Securimage
692
  if ($this->code == false) {
693
  $this->code = $this->generateCode($this->code_length);
694
  }
695
-
696
  $this->code_display = $this->code;
697
  $this->code = ($this->case_sensitive) ? $this->code : strtolower($this->code);
698
  } // default
699
  }
700
-
701
  $this->saveData();
702
  }
703
-
704
  /**
705
  * Draws the captcha code on the image
706
  */
@@ -708,7 +1186,7 @@ class Securimage
708
  {
709
  $width2 = $this->image_width * $this->iscale;
710
  $height2 = $this->image_height * $this->iscale;
711
-
712
  if (!is_readable($this->ttf_file)) {
713
  imagestring($this->im, 4, 10, ($this->image_height / 2) - 5, 'Failed to load TTF font file!', $this->gdtextcolor);
714
  } else {
@@ -732,13 +1210,13 @@ class Securimage
732
  imagettftext($this->im, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
733
  }
734
  }
735
-
736
  // DEBUG
737
  //$this->im = $this->tmpimg;
738
  //$this->output();
739
-
740
  }
741
-
742
  /**
743
  * Copies the captcha image to the final image with distortion applied
744
  */
@@ -747,13 +1225,13 @@ class Securimage
747
  $numpoles = 3; // distortion factor
748
  // make array of poles AKA attractor points
749
  for ($i = 0; $i < $numpoles; ++ $i) {
750
- $px[$i] = rand($this->image_width * 0.2, $this->image_width * 0.8);
751
- $py[$i] = rand($this->image_height * 0.2, $this->image_height * 0.8);
752
- $rad[$i] = rand($this->image_height * 0.2, $this->image_height * 0.8);
753
  $tmp = ((- $this->frand()) * 0.15) - .15;
754
  $amp[$i] = $this->perturbation * $tmp;
755
  }
756
-
757
  $bgCol = imagecolorat($this->tmpimg, 0, 0);
758
  $width2 = $this->iscale * $this->image_width;
759
  $height2 = $this->iscale * $this->image_height;
@@ -789,7 +1267,7 @@ class Securimage
789
  }
790
  }
791
  }
792
-
793
  /**
794
  * Draws distorted lines on the image
795
  */
@@ -798,13 +1276,13 @@ class Securimage
798
  for ($line = 0; $line < $this->num_lines; ++ $line) {
799
  $x = $this->image_width * (1 + $line) / ($this->num_lines + 1);
800
  $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines;
801
- $y = rand($this->image_height * 0.1, $this->image_height * 0.9);
802
-
803
  $theta = ($this->frand() - 0.5) * M_PI * 0.7;
804
  $w = $this->image_width;
805
- $len = rand($w * 0.4, $w * 0.7);
806
- $lwid = rand(0, 2);
807
-
808
  $k = $this->frand() * 0.6 + 0.2;
809
  $k = $k * $k * 0.5;
810
  $phi = $this->frand() * 6.28;
@@ -815,10 +1293,10 @@ class Securimage
815
  $amp = 1.5 * $this->frand() / ($k + 5.0 / $len);
816
  $x0 = $x - 0.5 * $len * cos($theta);
817
  $y0 = $y - 0.5 * $len * sin($theta);
818
-
819
  $ldx = round(- $dy * $lwid);
820
  $ldy = round($dx * $lwid);
821
-
822
  for ($i = 0; $i < $n; ++ $i) {
823
  $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi);
824
  $y = $y0 + $i * $dy - $amp * $dx * sin($k * $i * $step + $phi);
@@ -826,7 +1304,7 @@ class Securimage
826
  }
827
  }
828
  }
829
-
830
  /**
831
  * Draws random noise on the image
832
  */
@@ -839,24 +1317,24 @@ class Securimage
839
  }
840
 
841
  $t0 = microtime(true);
842
-
843
  $noise_level *= 125; // an arbitrary number that works well on a 1-10 scale
844
-
845
  $points = $this->image_width * $this->image_height * $this->iscale;
846
  $height = $this->image_height * $this->iscale;
847
  $width = $this->image_width * $this->iscale;
848
  for ($i = 0; $i < $noise_level; ++$i) {
849
- $x = rand(10, $width);
850
- $y = rand(10, $height);
851
- $size = rand(7, 10);
852
  if ($x - $size <= 0 && $y - $size <= 0) continue; // dont cover 0,0 since it is used by imagedistortedcopy
853
  imagefilledarc($this->tmpimg, $x, $y, $size, $size, 0, 360, $this->gdnoisecolor, IMG_ARC_PIE);
854
  }
855
-
856
  $t1 = microtime(true);
857
-
858
  $t = $t1 - $t0;
859
-
860
  /*
861
  // DEBUG
862
  imagestring($this->tmpimg, 5, 25, 30, "$t", $this->gdnoisecolor);
@@ -865,107 +1343,153 @@ class Securimage
865
  exit;
866
  */
867
  }
868
-
869
  /**
870
  * Print signature text on image
871
  */
872
  protected function addSignature()
873
- {
874
  $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature);
875
  $textlen = $bbox[2] - $bbox[0];
876
  $x = $this->image_width - $textlen - 5;
877
  $y = $this->image_height - 3;
878
-
879
- imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature);
880
  }
881
-
882
  /**
883
  * Sends the appropriate image and cache headers and outputs image to the browser
884
  */
885
  protected function output()
886
  {
887
- header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
888
- header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
889
- header("Cache-Control: no-store, no-cache, must-revalidate");
890
- header("Cache-Control: post-check=0, pre-check=0", false);
891
- header("Pragma: no-cache");
892
-
893
- switch ($this->image_type) {
894
- case self::SI_IMAGE_JPEG:
895
- header("Content-Type: image/jpeg");
896
- imagejpeg($this->im, null, 90);
897
- break;
898
- case self::SI_IMAGE_GIF:
899
- header("Content-Type: image/gif");
900
- imagegif($this->im);
901
- break;
902
- default:
903
- header("Content-Type: image/png");
904
- imagepng($this->im);
905
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
906
  }
907
-
908
  imagedestroy($this->im);
909
- exit();
 
 
910
  }
911
-
912
  /**
913
  * Gets the code and returns the binary audio file for the stored captcha code
914
- * @param string $format WAV only
 
915
  */
916
- protected function getAudibleCode($format = 'wav')
917
  {
918
- // override any format other than wav for now
919
- // this is due to security issues with MP3 files
920
- $format = 'wav';
921
-
922
  $letters = array();
923
- $code = $this->getCode();
924
 
925
- if ($code == '') {
926
- $this->createCode();
927
- $code = $this->getCode();
 
 
 
 
928
  }
929
 
930
- for($i = 0; $i < strlen($code); ++$i) {
931
- $letters[] = $code{$i};
932
- }
933
-
934
- if ($format == 'mp3') {
935
- return $this->generateMP3($letters);
 
 
936
  } else {
 
 
 
 
 
 
 
 
 
 
 
937
  return $this->generateWAV($letters);
 
 
938
  }
939
  }
940
 
941
  /**
942
  * Gets a captcha code from a wordlist
943
  */
944
- protected function readCodeFromFile()
945
  {
946
- $fp = @fopen($this->wordlist_file, 'rb');
947
  if (!$fp) return false;
948
 
949
  $fsize = filesize($this->wordlist_file);
950
  if ($fsize < 128) return false; // too small of a list to be effective
951
-
952
- fseek($fp, rand(0, $fsize - 64), SEEK_SET); // seek to a random position of file from 0 to filesize-64
953
- $data = fread($fp, 64); // read a chunk from our random position
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
954
  fclose($fp);
955
- $data = preg_replace("/\r?\n/", "\n", $data);
956
-
957
- $start = @strpos($data, "\n", rand(0, 56)) + 1; // random start position
958
- $end = @strpos($data, "\n", $start); // find end of word
959
 
960
- if ($start === false) {
961
- return false;
962
- } else if ($end === false) {
963
- $end = strlen($data);
964
  }
965
-
966
- return strtolower(substr($data, $start, $end - $start)); // return a line of the file
967
  }
968
-
969
  /**
970
  * Generates a random captcha code from the set character set
971
  */
@@ -973,172 +1497,400 @@ class Securimage
973
  {
974
  $code = '';
975
 
976
- for($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i) {
977
- $code .= $this->charset{rand(0, $cslen - 1)};
 
 
 
 
 
 
978
  }
979
-
980
- //return 'testing'; // debug, set the code to given string
981
-
982
  return $code;
983
  }
984
-
985
  /**
986
  * Checks the entered code against the value stored in the session or sqlite database, handles case sensitivity
987
  * Also clears the stored codes if the code was entered correctly to prevent re-use
988
  */
989
  protected function validate()
990
  {
991
- $code = $this->getCode();
992
- // returns stored code, or an empty string if no stored code was found
993
- // checks the session and sqlite database if enabled
994
-
 
 
 
 
995
  if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code)) {
996
  // case sensitive was set from securimage_show.php but not in class
997
  // the code saved in the session has capitals so set case sensitive to true
998
  $this->case_sensitive = true;
999
  }
1000
-
1001
  $code_entered = trim( (($this->case_sensitive) ? $this->code_entered
1002
  : strtolower($this->code_entered))
1003
  );
1004
  $this->correct_code = false;
1005
-
1006
  if ($code != '') {
 
 
 
 
 
 
1007
  if ($code == $code_entered) {
1008
  $this->correct_code = true;
1009
- $_SESSION['securimage_code_value'][$this->namespace] = '';
1010
- $_SESSION['securimage_code_ctime'][$this->namespace] = '';
 
 
1011
  $this->clearCodeFromDatabase();
1012
  }
1013
  }
1014
  }
1015
-
1016
- /**
1017
- * Return the code from the session or sqlite database if used. If none exists yet, an empty string is returned
1018
- */
1019
- protected function getCode()
1020
- {
1021
- $code = '';
1022
-
1023
- if (isset($_SESSION['securimage_code_value'][$this->namespace]) &&
1024
- trim($_SESSION['securimage_code_value'][$this->namespace]) != '') {
1025
- if ($this->isCodeExpired(
1026
- $_SESSION['securimage_code_ctime'][$this->namespace]) == false) {
1027
- $code = $_SESSION['securimage_code_value'][$this->namespace];
1028
- }
1029
- } else if ($this->use_sqlite_db == true && function_exists('sqlite_open')) {
1030
- // no code in session - may mean user has cookies turned off
1031
- $this->openDatabase();
1032
- $code = $this->getCodeFromDatabase();
1033
- } else { /* no code stored in session or sqlite database, validation will fail */ }
1034
-
1035
- return $code;
1036
- }
1037
-
1038
  /**
1039
  * Save data to session namespace and database if used
1040
  */
1041
  protected function saveData()
1042
  {
1043
- $_SESSION['securimage_code_value'][$this->namespace] = $this->code;
1044
- $_SESSION['securimage_code_ctime'][$this->namespace] = time();
 
 
 
 
 
 
 
 
 
1045
 
1046
- $this->saveCodeToDatabase();
 
 
1047
  }
1048
-
1049
  /**
1050
  * Saves the code to the sqlite database
1051
  */
1052
  protected function saveCodeToDatabase()
1053
  {
1054
  $success = false;
1055
-
1056
  $this->openDatabase();
1057
-
1058
- if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
1059
- $ip = $_SERVER['REMOTE_ADDR'];
1060
- $time = time();
1061
- $code = $_SESSION['securimage_code_value'][$this->namespace]; // if cookies are disabled the session still exists at this point
1062
- $success = sqlite_query($this->sqlite_handle,
1063
- "INSERT OR REPLACE INTO codes(ip, code, namespace, created)
1064
- VALUES('$ip', '$code', '{$this->namespace}', $time)");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1065
  }
1066
 
1067
  return $success !== false;
1068
  }
1069
-
1070
  /**
1071
  * Open sqlite database
1072
  */
1073
  protected function openDatabase()
1074
  {
1075
- $this->sqlite_handle = false;
1076
 
1077
- if ($this->use_sqlite_db && function_exists('sqlite_open')) {
1078
- $this->sqlite_handle = sqlite_open($this->sqlite_database, 0666, $error);
1079
 
1080
- if ($this->sqlite_handle !== false) {
1081
- $res = sqlite_query($this->sqlite_handle, "PRAGMA table_info(codes)");
1082
- if (sqlite_num_rows($res) == 0) {
1083
- sqlite_query($this->sqlite_handle, "CREATE TABLE codes (ip VARCHAR(32) PRIMARY KEY, code VARCHAR(32) NOT NULL, namespace VARCHAR(32) NOT NULL, created INTEGER)");
 
 
 
 
 
 
 
 
 
1084
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1085
  }
1086
 
1087
- return $this->sqlite_handle != false;
 
 
 
 
 
 
 
1088
  }
 
 
 
 
 
1089
 
1090
- return $this->sqlite_handle;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1091
  }
1092
 
1093
  /**
1094
- * Get a code from the sqlite database for ip address
 
 
 
 
1095
  */
1096
  protected function getCodeFromDatabase()
1097
  {
1098
  $code = '';
1099
 
1100
- if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
1101
- $ip = $_SERVER['REMOTE_ADDR'];
1102
- $ns = sqlite_escape_string($this->namespace);
1103
-
1104
- $res = sqlite_query($this->sqlite_handle, "SELECT * FROM codes WHERE ip = '$ip' AND namespace = '$ns'");
1105
- if ($res && sqlite_num_rows($res) > 0) {
1106
- $res = sqlite_fetch_array($res);
1107
-
1108
- if ($this->isCodeExpired($res['created']) == false) {
1109
- $code = $res['code'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1110
  }
1111
  }
1112
  }
 
1113
  return $code;
1114
  }
1115
-
1116
  /**
1117
  * Remove an entered code from the database
1118
  */
1119
  protected function clearCodeFromDatabase()
1120
  {
1121
- if (is_resource($this->sqlite_handle)) {
1122
  $ip = $_SERVER['REMOTE_ADDR'];
1123
- $ns = sqlite_escape_string($this->namespace);
 
 
 
 
 
 
 
1124
 
1125
- sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE ip = '$ip' AND namespace = '$ns'");
 
 
 
 
 
 
1126
  }
1127
  }
1128
-
1129
  /**
1130
  * Deletes old codes from sqlite database
1131
  */
1132
  protected function purgeOldCodesFromDatabase()
1133
  {
1134
- if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
1135
  $now = time();
1136
  $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time;
1137
 
1138
- sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE $now - created > $limit");
 
 
 
 
 
1139
  }
1140
  }
1141
-
1142
  /**
1143
  * Checks to see if the captcha code has expired and cannot be used
1144
  * @param unknown_type $creation_time
@@ -1146,27 +1898,16 @@ class Securimage
1146
  protected function isCodeExpired($creation_time)
1147
  {
1148
  $expired = true;
1149
-
1150
  if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) {
1151
  $expired = false;
1152
  } else if (time() - $creation_time < $this->expiry_time) {
1153
  $expired = false;
1154
  }
1155
-
1156
  return $expired;
1157
  }
1158
-
1159
- /**
1160
- *
1161
- * Generate an MP3 audio file of the captcha image
1162
- *
1163
- * @deprecated 3.0
1164
- */
1165
- protected function generateMP3()
1166
- {
1167
- return false;
1168
- }
1169
-
1170
  /**
1171
  * Generate a wav file given the $letters in the code
1172
  * @todo Add ability to merge 2 sound files together to have random background sounds
@@ -1175,145 +1916,148 @@ class Securimage
1175
  */
1176
  protected function generateWAV($letters)
1177
  {
1178
- $data_len = 0;
1179
- $files = array();
1180
- $out_data = '';
1181
- $out_channels = 0;
1182
- $out_samplert = 0;
1183
- $out_bpersample = 0;
1184
- $numSamples = 0;
1185
- $removeChunks = array('LIST', 'DISP', 'NOTE');
1186
-
1187
- for ($i = 0; $i < sizeof($letters); ++$i) {
1188
- $letter = $letters[$i];
1189
- $filename = $this->audio_path . strtoupper($letter) . '.wav';
1190
- $file = array();
1191
- $data = @file_get_contents($filename);
1192
-
1193
- if ($data === false) {
1194
- // echo "Failed to read $filename";
1195
- return $this->audioError();
1196
- }
1197
 
1198
- $header = substr($data, 0, 36);
1199
- $info = unpack('NChunkID/VChunkSize/NFormat/NSubChunk1ID/'
1200
- .'VSubChunk1Size/vAudioFormat/vNumChannels/'
1201
- .'VSampleRate/VByteRate/vBlockAlign/vBitsPerSample',
1202
- $header);
1203
-
1204
- $dataPos = strpos($data, 'data');
1205
- $out_channels = $info['NumChannels'];
1206
- $out_samplert = $info['SampleRate'];
1207
- $out_bpersample = $info['BitsPerSample'];
1208
-
1209
- if ($dataPos === false) {
1210
- // wav file with no data?
1211
- // echo "Failed to find DATA segment in $filename";
1212
- return $this->audioError();
1213
- }
1214
-
1215
- if ($info['AudioFormat'] != 1) {
1216
- // only work with PCM audio
1217
- // echo "$filename was not PCM audio, only PCM is supported";
1218
- return $this->audioError();
1219
- }
1220
-
1221
- if ($info['SubChunk1Size'] != 16 && $info['SubChunk1Size'] != 18) {
1222
- // probably unsupported extension
1223
- // echo "Bad SubChunk1Size in $filename - Size was {$info['SubChunk1Size']}";
1224
- return $this->audioError();
1225
- }
1226
-
1227
- if ($info['SubChunk1Size'] > 16) {
1228
- $header .= substr($data, 36, $info['SubChunk1Size'] - 16);
1229
- }
1230
-
1231
- if ($i == 0) {
1232
- // create the final file's header, size will be adjusted later
1233
- $out_data = $header . 'data';
1234
  }
1235
-
1236
- $removed = 0;
1237
-
1238
- foreach($removeChunks as $chunk) {
1239
- $chunkPos = strpos($data, $chunk);
1240
- if ($chunkPos !== false) {
1241
- $listSize = unpack('VSize', substr($data, $chunkPos + 4, 4));
1242
-
1243
- $data = substr($data, 0, $chunkPos) .
1244
- substr($data, $chunkPos + 8 + $listSize['Size']);
1245
-
1246
- $removed += $listSize['Size'] + 8;
 
 
 
 
 
 
 
 
 
 
 
 
 
1247
  }
 
 
 
 
 
 
 
 
1248
  }
1249
-
1250
- $dataSize = unpack('VSubchunk2Size', substr($data, $dataPos + 4, 4));
1251
- $dataSize['Subchunk2Size'] -= $removed;
1252
- $out_data .= substr($data, $dataPos + 8, $dataSize['Subchunk2Size'] * ($out_bpersample / 8));
1253
- $numSamples += $dataSize['Subchunk2Size'];
1254
  }
1255
 
1256
- $filesize = strlen($out_data);
1257
- $chunkSize = $filesize - 8;
1258
- $dataCSize = $numSamples;
1259
-
1260
- $out_data = substr_replace($out_data, pack('V', $chunkSize), 4, 4);
1261
- $out_data = substr_replace($out_data, pack('V', $numSamples), 40 + ($info['SubChunk1Size'] - 16), 4);
1262
 
1263
- $this->scrambleAudioData($out_data, 'wav');
1264
-
1265
- return $out_data;
 
 
1266
  }
1267
-
1268
- /**
1269
- * Randomizes the audio data to add noise and prevent binary recognition
1270
- * @param string $data The binary audio file data
1271
- * @param string $format The format of the sound file (wav only)
1272
- */
1273
- protected function scrambleAudioData(&$data, $format)
1274
  {
1275
- $start = strpos($data, 'data') + 4; // look for "data" indicator
1276
- if ($start === false) $start = 44; // if not found assume 44 byte header
1277
-
1278
- $start += rand(1, 4); // randomize starting offset
1279
- $datalen = strlen($data) - $start;
1280
- $step = 1;
1281
-
1282
- for ($i = $start; $i < $datalen; $i += $step) {
1283
- $ch = ord($data{$i});
1284
- if ($ch == 0 || $ch == 255) continue;
1285
-
1286
- if ($ch < 16 || $ch > 239) {
1287
- $ch += rand(-6, 6);
1288
- } else {
1289
- $ch += rand(-12, 12);
1290
  }
1291
-
1292
- if ($ch < 0) $ch = 0; else if ($ch > 255) $ch = 255;
1293
 
1294
- $data{$i} = chr($ch);
1295
-
1296
- $step = rand(1,4);
 
 
 
1297
  }
1298
 
1299
- return $data;
1300
  }
1301
-
1302
  /**
1303
  * Return a wav file saying there was an error generating file
1304
- *
1305
  * @return string The binary audio contents
1306
  */
1307
  protected function audioError()
1308
  {
1309
- return @file_get_contents(dirname(__FILE__) . '/audio/error.wav');
1310
  }
1311
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1312
  function frand()
1313
  {
1314
- return 0.0001 * rand(0,9999);
1315
  }
1316
-
1317
  /**
1318
  * Convert an html color code to a Securimage_Color
1319
  * @param string $color
@@ -1335,6 +2079,34 @@ class Securimage
1335
  return new Securimage_Color($default);
1336
  }
1337
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1338
  }
1339
 
1340
 
@@ -1360,14 +2132,14 @@ class Securimage_Color
1360
  * when passing 3 arguments, specify each RGB component (from 0-255) individually.<br />
1361
  * $color = new Securimage_Color('#0080FF') or <br />
1362
  * $color = new Securimage_Color(0, 128, 255)
1363
- *
1364
  * @param string $color
1365
  * @throws Exception
1366
  */
1367
  public function __construct($color = '#ffffff')
1368
  {
1369
  $args = func_get_args();
1370
-
1371
  if (sizeof($args) == 0) {
1372
  $this->r = 255;
1373
  $this->g = 255;
@@ -1377,13 +2149,13 @@ class Securimage_Color
1377
  if (substr($color, 0, 1) == '#') {
1378
  $color = substr($color, 1);
1379
  }
1380
-
1381
  if (strlen($color) != 3 && strlen($color) != 6) {
1382
  throw new InvalidArgumentException(
1383
  'Invalid HTML color code passed to Securimage_Color'
1384
  );
1385
  }
1386
-
1387
  $this->constructHTML($color);
1388
  } else if (sizeof($args) == 3) {
1389
  $this->constructRGB($args[0], $args[1], $args[2]);
@@ -1393,7 +2165,7 @@ class Securimage_Color
1393
  );
1394
  }
1395
  }
1396
-
1397
  /**
1398
  * Construct from an rgb triplet
1399
  * @param int $red The red component, 0-255
@@ -1408,12 +2180,12 @@ class Securimage_Color
1408
  if ($green > 255) $green = 255;
1409
  if ($blue < 0) $blue = 0;
1410
  if ($blue > 255) $blue = 255;
1411
-
1412
  $this->r = $red;
1413
  $this->g = $green;
1414
  $this->b = $blue;
1415
  }
1416
-
1417
  /**
1418
  * Construct from an html hex color code
1419
  * @param string $color
@@ -1427,9 +2199,9 @@ class Securimage_Color
1427
  } else {
1428
  $red = substr($color, 0, 2);
1429
  $green = substr($color, 2, 2);
1430
- $blue = substr($color, 4, 2);
1431
  }
1432
-
1433
  $this->r = hexdec($red);
1434
  $this->g = hexdec($green);
1435
  $this->b = hexdec($blue);
6
  * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br />
7
  * File: securimage.php<br />
8
  *
9
+ * Copyright (c) 2013, Drew Phillips
10
  * All rights reserved.
11
+ *
12
  * Redistribution and use in source and binary forms, with or without modification,
13
  * are permitted provided that the following conditions are met:
14
+ *
15
  * - Redistributions of source code must retain the above copyright notice,
16
  * this list of conditions and the following disclaimer.
17
  * - Redistributions in binary form must reproduce the above copyright notice,
18
  * this list of conditions and the following disclaimer in the documentation
19
  * and/or other materials provided with the distribution.
20
+ *
21
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
39
  * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
40
  * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
41
  * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
42
+ * @copyright 2013 Drew Phillips
43
  * @author Drew Phillips <drew@drew-phillips.com>
44
+ * @version 3.5 (April 2013)
45
  * @package Securimage
46
  *
47
  */
48
 
49
  /**
50
  ChangeLog
 
 
 
51
 
52
+ 3.5
53
+ - Release new version
54
+ - MB string support for charlist
55
+ - Modify audio file path to use language directories
56
+ - Changed default captcha appearance
57
+
58
+ 3.2RC4
59
+ - Add MySQL, PostgreSQL, and SQLite3 support for database storage
60
+ - Deprecate "use_sqlite_db" option and remove SQLite2/sqlite_* functions
61
+ - Add new captcha type that displays 2 dictionary words on one image
62
+ - Update examples
63
+
64
+ 3.2RC3
65
+ - Fix canSendHeaders() check which was breaking if a PHP startup error was issued
66
+
67
+ 3.2RC2
68
+ - Add error handler (https://github.com/dapphp/securimage/issues/15)
69
+ - Fix flash examples to use the correct value name for audio parameter
70
+
71
+ 3.2RC1
72
+ - New audio captcha code. Faster, fully dynamic audio, full WAV support
73
+ (Paul Voegler, Drew Phillips) <http://voegler.eu/pub/audio>
74
+ - New Flash audio streaming button. User defined image and size supported
75
+ - Additional options for customizing captcha (noise_level, send_headers,
76
+ no_exit, no_session, display_value
77
+ - Add captcha ID support. Uses sqlite and unique captcha IDs to track captchas,
78
+ no session used
79
+ - Add static methods for creating and validating captcha by ID
80
+ - Automatic clearing of old codes from SQLite database
81
+
82
+ 3.0.3Beta
83
+ - Add improved mixing function to WavFile class (Paul Voegler)
84
+ - Improve performance and security of captcha audio (Paul Voegler, Drew Phillips)
85
+ - Add option to use random file as background noise in captcha audio
86
+ - Add new securimage options for audio files
87
+
88
+ 3.0.2Beta
89
+ - Fix issue with session variables when upgrading from 2.0 - 3.0
90
+ - Improve audio captcha, switch to use WavFile class, make mathematical captcha audio work
91
+
92
+ 3.0.1
93
+ - Bugfix: removed use of deprecated variable in addSignature method that would cause errors with display_errors on
94
+
95
  3.0
96
  - Rewrite class using PHP5 OOP
97
  - Remove support for GD fonts, require FreeType
123
  - Flash button to stream mp3 audio (Douglas Walsh www.douglaswalsh.net)
124
  - Audio output is mp3 format by default
125
  - Change font to AlteHaasGrotesk by yann le coroller
126
+ - Some code cleanup
127
 
128
  1.0.4 (unreleased)
129
  - Ability to output audible codes in mp3 format to stream from flash
153
  /**
154
  * Securimage CAPTCHA Class.
155
  *
156
+ * @version 3.5
157
  * @package Securimage
158
  * @subpackage classes
159
  * @author Drew Phillips <drew@drew-phillips.com>
164
  // All of the public variables below are securimage options
165
  // They can be passed as an array to the Securimage constructor, set below,
166
  // or set from securimage_show.php and securimage_play.php
167
+
168
  /**
169
  * Renders captcha as a JPEG image
170
  * @var int
180
  * @var int
181
  */
182
  const SI_IMAGE_GIF = 3;
183
+
184
  /**
185
  * Create a normal alphanumeric captcha
186
  * @var int
191
  * @var int
192
  */
193
  const SI_CAPTCHA_MATHEMATIC = 1;
194
+ /**
195
+ * Create a word based captcha using 2 words
196
+ * @var int
197
+ */
198
+ const SI_CAPTCHA_WORDS = 2;
199
+
200
+ /**
201
+ * MySQL option identifier for database storage option
202
+ *
203
+ * @var string
204
+ */
205
+ const SI_DRIVER_MYSQL = 'mysql';
206
+
207
+ /**
208
+ * PostgreSQL option identifier for database storage option
209
+ *
210
+ * @var string
211
+ */
212
+ const SI_DRIVER_PGSQL = 'pgsql';
213
+
214
+ /**
215
+ * SQLite option identifier for database storage option
216
+ *
217
+ * @var string
218
+ */
219
+ const SI_DRIVER_SQLITE3 = 'sqlite';
220
+
221
+ /*%*********************************************************************%*/
222
+ // Properties
223
 
224
  /**
225
  * The width of the captcha image
254
  public $line_color = '#707070';
255
  /**
256
  * The color of the noise that is drawn
257
+ * @var Securimage_Color
258
  */
259
  public $noise_color = '#707070';
260
+
261
  /**
262
  * How transparent to make the text 0 = completely opaque, 100 = invisible
263
  * @var int
264
  */
265
+ public $text_transparency_percentage = 20;
266
  /**
267
  * Whether or not to draw the text transparently, true = use transparency, false = no transparency
268
  * @var bool
269
  */
270
+ public $use_transparent_text = true;
271
+
272
  /**
273
  * The length of the captcha code
274
  * @var int
289
  * @var unknown_type
290
  */
291
  public $expiry_time = 900;
292
+
293
  /**
294
  * The session name securimage should use, only set this if your application uses a custom session name
295
  * It is recommended to set this value below so it is used by all securimage scripts
296
  * @var string
297
  */
298
  public $session_name = null;
299
+
300
  /**
301
  * true to use the wordlist file, false to generate random captcha codes
302
  * @var bool
307
  * The level of distortion, 0.75 = normal, 1.0 = very high distortion
308
  * @var double
309
  */
310
+ public $perturbation = 0.85;
311
  /**
312
  * How many lines to draw over the captcha code to increase security
313
  * @var int
314
  */
315
+ public $num_lines = 5;
316
  /**
317
  * The level of noise (random dots) to place on the image, 0-10
318
  * @var int
319
  */
320
+ public $noise_level = 2;
321
+
322
  /**
323
  * The signature text to draw on the bottom corner of the image
324
  * @var string
334
  * @var string
335
  */
336
  public $signature_font;
337
+
338
  /**
339
+ * DO NOT USE!!!
340
  * Use an SQLite database to store data (for users that do not support cookies)
341
  * @var bool
342
+ * @see Securimage::$use_sqlite_db
343
+ * @deprecated 3.2RC4
344
  */
345
  public $use_sqlite_db = false;
346
 
347
+ /**
348
+ * Use a database backend for code storage.
349
+ * Provides a fallback to users with cookies disabled.
350
+ * Required when using captcha IDs.
351
+ *
352
+ * @see Securimage::$database_driver
353
+ * @var bool
354
+ */
355
+ public $use_database = false;
356
+
357
+ /**
358
+ * Database driver to use for database support.
359
+ * Allowable values: 'mysql', 'pgsql', 'sqlite'.
360
+ * Default: sqlite
361
+ *
362
+ * @var string
363
+ */
364
+ public $database_driver = self::SI_DRIVER_SQLITE3;
365
+
366
+ /**
367
+ * Database host to connect to when using mysql or postgres
368
+ * On Linux use "localhost" for Unix domain socket, otherwise uses TCP/IP
369
+ * Does not apply to SQLite
370
+ *
371
+ * @var string
372
+ */
373
+ public $database_host = 'localhost';
374
+
375
+ /**
376
+ * Database username for connection (mysql, postgres only)
377
+ * Default is an empty string
378
+ *
379
+ * @var string
380
+ */
381
+ public $database_user = '';
382
+
383
+ /**
384
+ * Database password for connection (mysql, postgres only)
385
+ * Default is empty string
386
+ *
387
+ * @var string
388
+ */
389
+ public $database_pass = '';
390
+
391
+ /**
392
+ * Name of the database to select (mysql, postgres only)
393
+ *
394
+ * @see Securimage::$database_file for SQLite
395
+ * @var string
396
+ */
397
+ public $database_name = '';
398
+
399
+ /**
400
+ * Database table where captcha codes are stored
401
+ * Note: Securimage will attempt to create this table for you if it does
402
+ * not exist. If the table cannot be created, an E_USER_WARNING is emitted.
403
+ *
404
+ * @var string
405
+ */
406
+ public $database_table = 'captcha_codes';
407
+
408
+ /**
409
+ * Fully qualified path to the database file when using SQLite3.
410
+ * This value is only used when $database_driver == sqlite3 and does
411
+ * not apply when no database is used, or when using MySQL or PostgreSQL.
412
+ *
413
+ * @var string
414
+ */
415
+ public $database_file;
416
+
417
  /**
418
  * The type of captcha to create, either alphanumeric, or a math problem<br />
419
  * Securimage::SI_CAPTCHA_STRING or Securimage::SI_CAPTCHA_MATHEMATIC
420
  * @var int
421
  */
422
+ public $captcha_type = self::SI_CAPTCHA_STRING; // or self::SI_CAPTCHA_MATHEMATIC;
423
+
424
  /**
425
  * The captcha namespace, use this if you have multiple forms on a single page, blank if you do not use multiple forms on one page
426
  * @var string
428
  * <?php
429
  * // in securimage_show.php (create one show script for each form)
430
  * $img->namespace = 'contact_form';
431
+ *
432
  * // in form validator
433
  * $img->namespace = 'contact_form';
434
  * if ($img->check($code) == true) {
437
  * </code>
438
  */
439
  public $namespace;
440
+
441
  /**
442
  * The font file to use to draw the captcha code, leave blank for default font AHGBold.ttf
443
  * @var string
455
  public $background_directory;
456
  /**
457
  * The path to the SQLite database file to use, if $use_sqlite_database = true, should be chmod 666
458
+ * @deprecated 3.2RC4
459
  * @var string
460
  */
461
  public $sqlite_database;
463
  * The path to the securimage audio directory, can be set in securimage_play.php
464
  * @var string
465
  * <code>
466
+ * $img->audio_path = '/home/yoursite/public_html/securimage/audio/en/';
467
  * </code>
468
  */
469
  public $audio_path;
470
+ /**
471
+ * The path to the directory containing audio files that will be selected
472
+ * randomly and mixed with the captcha audio.
473
+ *
474
+ * @var string
475
+ */
476
+ public $audio_noise_path;
477
+ /**
478
+ * Whether or not to mix background noise files into captcha audio (true = mix, false = no)
479
+ * Mixing random background audio with noise can help improve security of audio captcha.
480
+ * Default: securimage/audio/noise
481
+ *
482
+ * @since 3.0.3
483
+ * @see Securimage::$audio_noise_path
484
+ * @var bool
485
+ */
486
+ public $audio_use_noise;
487
+ /**
488
+ * The method and threshold (or gain factor) used to normalize the mixing with background noise.
489
+ * See http://www.voegler.eu/pub/audio/ for more information.
490
+ *
491
+ * Valid: <ul>
492
+ * <li> >= 1 - Normalize by multiplying by the threshold (boost - positive gain). <br />
493
+ * A value of 1 in effect means no normalization (and results in clipping). </li>
494
+ * <li> <= -1 - Normalize by dividing by the the absolute value of threshold (attenuate - negative gain). <br />
495
+ * A factor of 2 (-2) is about 6dB reduction in volume.</li>
496
+ * <li> [0, 1) - (open inverval - not including 1) - The threshold
497
+ * above which amplitudes are comressed logarithmically. <br />
498
+ * e.g. 0.6 to leave amplitudes up to 60% "as is" and compress above. </li>
499
+ * <li> (-1, 0) - (open inverval - not including -1 and 0) - The threshold
500
+ * above which amplitudes are comressed linearly. <br />
501
+ * e.g. -0.6 to leave amplitudes up to 60% "as is" and compress above. </li></ul>
502
+ *
503
+ * Default: 0.6
504
+ *
505
+ * @since 3.0.4
506
+ * @var float
507
+ */
508
+ public $audio_mix_normalization = 0.6;
509
+ /**
510
+ * Whether or not to degrade audio by introducing random noise (improves security of audio captcha)
511
+ * Default: true
512
+ *
513
+ * @since 3.0.3
514
+ * @var bool
515
+ */
516
+ public $degrade_audio;
517
+ /**
518
+ * Minimum delay to insert between captcha audio letters in milliseconds
519
+ *
520
+ * @since 3.0.3
521
+ * @var float
522
+ */
523
+ public $audio_gap_min = 0;
524
+ /**
525
+ * Maximum delay to insert between captcha audio letters in milliseconds
526
+ *
527
+ * @since 3.0.3
528
+ * @var float
529
+ */
530
+ public $audio_gap_max = 600;
531
+
532
+ /**
533
+ * Captcha ID if using static captcha
534
+ * @var string Unique captcha id
535
+ */
536
+ protected static $_captchaId = null;
537
 
 
 
538
  protected $im;
539
  protected $tmpimg;
540
  protected $bgimg;
541
  protected $iscale = 5;
542
+
543
+ public $securimage_path = null;
544
+
545
+ /**
546
+ * The captcha challenge value (either the case-sensitive/insensitive word captcha, or the solution to the math captcha)
547
+ *
548
+ * @var string Captcha challenge value
549
+ */
550
  protected $code;
551
+
552
+ /**
553
+ * The display value of the captcha to draw on the image (the word captcha, or the math equation to present to the user)
554
+ *
555
+ * @var string Captcha display value to draw on the image
556
+ */
557
  protected $code_display;
558
+
559
+ /**
560
+ * A value that can be passed to the constructor that can be used to generate a captcha image with a given value
561
+ * This value does not get stored in the session or database and is only used when calling Securimage::show().
562
+ * If a display_value was passed to the constructor and the captcha image is generated, the display_value will be used
563
+ * as the string to draw on the captcha image. Used only if captcha codes are generated and managed by a 3rd party app/library
564
+ *
565
+ * @var string Captcha code value to display on the image
566
+ */
567
+ public $display_value;
568
+
569
+ /**
570
+ * Captcha code supplied by user [set from Securimage::check()]
571
+ *
572
+ * @var string
573
+ */
574
  protected $captcha_code;
575
+
576
+ /**
577
+ * Flag that can be specified telling securimage not to call exit after generating a captcha image or audio file
578
+ *
579
+ * @var bool If true, script will not terminate; if false script will terminate (default)
580
+ */
581
+ protected $no_exit;
582
+
583
+ /**
584
+ * Flag indicating whether or not a PHP session should be started and used
585
+ *
586
+ * @var bool If true, no session will be started; if false, session will be started and used to store data (default)
587
+ */
588
+ protected $no_session;
589
+
590
+ /**
591
+ * Flag indicating whether or not HTTP headers will be sent when outputting captcha image/audio
592
+ *
593
+ * @var bool If true (default) headers will be sent, if false, no headers are sent
594
+ */
595
+ protected $send_headers;
596
+
597
+ /**
598
+ * PDO connection when a database is used
599
+ *
600
+ * @var resource
601
+ */
602
+ protected $pdo_conn;
603
+
604
+ // gd color resources that are allocated for drawing the image
605
  protected $gdbgcolor;
606
  protected $gdtextcolor;
607
  protected $gdlinecolor;
608
  protected $gdsignaturecolor;
609
+
610
  /**
611
  * Create a new securimage object, pass options to set in the constructor.<br />
612
  * This can be used to display a captcha, play an audible captcha, or validate an entry
619
  * 'noise_level' => 3,
620
  * 'font_file' => Securimage::getPath() . '/custom.ttf'
621
  * );
622
+ *
623
  * $img = new Securimage($options);
624
  * </code>
625
  */
626
  public function __construct($options = array())
627
  {
628
  $this->securimage_path = dirname(__FILE__);
629
+
630
  if (is_array($options) && sizeof($options) > 0) {
631
  foreach($options as $prop => $val) {
632
+ if ($prop == 'captchaId') {
633
+ Securimage::$_captchaId = $val;
634
+ $this->use_database = true;
635
+ } else if ($prop == 'use_sqlite_db') {
636
+ trigger_error("The use_sqlite_db option is deprecated, use 'use_database' instead", E_USER_NOTICE);
637
+ } else {
638
+ $this->$prop = $val;
639
+ }
640
  }
641
  }
642
 
646
  $this->noise_color = $this->initColor($this->noise_color, '#616161');
647
  $this->signature_color = $this->initColor($this->signature_color, '#616161');
648
 
649
+ if (is_null($this->ttf_file)) {
650
  $this->ttf_file = $this->securimage_path . '/AHGBold.ttf';
651
  }
652
+
653
  $this->signature_font = $this->ttf_file;
654
+
655
+ if (is_null($this->wordlist_file)) {
656
  $this->wordlist_file = $this->securimage_path . '/words/words.txt';
657
  }
658
+
659
+ if (is_null($this->database_file)) {
660
+ $this->database_file = $this->securimage_path . '/database/securimage.sq3';
661
  }
662
+
663
+ if (is_null($this->audio_path)) {
664
+ $this->audio_path = $this->securimage_path . '/audio/en/';
665
  }
666
+
667
+ if (is_null($this->audio_noise_path)) {
668
+ $this->audio_noise_path = $this->securimage_path . '/audio/noise/';
669
+ }
670
+
671
+ if (is_null($this->audio_use_noise)) {
672
+ $this->audio_use_noise = true;
673
+ }
674
+
675
+ if (is_null($this->degrade_audio)) {
676
+ $this->degrade_audio = true;
677
+ }
678
+
679
+ if (is_null($this->code_length) || (int)$this->code_length < 1) {
680
  $this->code_length = 6;
681
  }
682
+
683
+ if (is_null($this->perturbation) || !is_numeric($this->perturbation)) {
684
  $this->perturbation = 0.75;
685
  }
686
+
687
+ if (is_null($this->namespace) || !is_string($this->namespace)) {
688
  $this->namespace = 'default';
689
  }
690
 
691
+ if (is_null($this->no_exit)) {
692
+ $this->no_exit = false;
693
+ }
694
+
695
+ if (is_null($this->no_session)) {
696
+ $this->no_session = false;
697
+ }
698
+
699
+ if (is_null($this->send_headers)) {
700
+ $this->send_headers = true;
701
+ }
702
+
703
+ if ($this->no_session != true) {
704
+ // Initialize session or attach to existing
705
+ if ( session_id() == '' ) { // no session has been started yet, which is needed for validation
706
+ if (!is_null($this->session_name) && trim($this->session_name) != '') {
707
+ session_name(trim($this->session_name)); // set session name if provided
708
+ }
709
+ session_start();
710
  }
 
711
  }
712
  }
713
+
714
  /**
715
  * Return the absolute path to the Securimage directory
716
  * @return string The path to the securimage base directory
719
  {
720
  return dirname(__FILE__);
721
  }
722
+
723
+ /**
724
+ * Generate a new captcha ID or retrieve the current ID
725
+ *
726
+ * @param $new bool If true, generates a new challenge and returns and ID
727
+ * @param $options array Additional options to be passed to Securimage.
728
+ * Must include database options if not set directly in securimage.php
729
+ *
730
+ * @return null|string Returns null if no captcha id set and new was false, or string captcha ID
731
+ */
732
+ public static function getCaptchaId($new = true, array $options = array())
733
+ {
734
+ if (is_null($new) || (bool)$new == true) {
735
+ $id = sha1(uniqid($_SERVER['REMOTE_ADDR'], true));
736
+ $opts = array('no_session' => true,
737
+ 'use_database' => true);
738
+ if (sizeof($options) > 0) $opts = array_merge($options, $opts);
739
+ $si = new self($opts);
740
+ Securimage::$_captchaId = $id;
741
+ $si->createCode();
742
+
743
+ return $id;
744
+ } else {
745
+ return Securimage::$_captchaId;
746
+ }
747
+ }
748
+
749
+ /**
750
+ * Validate a captcha code input against a captcha ID
751
+ *
752
+ * @param string $id The captcha ID to check
753
+ * @param string $value The captcha value supplied by the user
754
+ * @param array $options Array of options to construct Securimage with.
755
+ * Options must include database options if they are not set in securimage.php
756
+ *
757
+ * @see Securimage::$database_driver
758
+ * @return bool true if the code was valid for the given captcha ID, false if not or if database failed to open
759
+ */
760
+ public static function checkByCaptchaId($id, $value, array $options = array())
761
+ {
762
+ $opts = array('captchaId' => $id,
763
+ 'no_session' => true,
764
+ 'use_database' => true);
765
+
766
+ if (sizeof($options) > 0) $opts = array_merge($options, $opts);
767
+
768
+ $si = new self($opts);
769
+
770
+ if ($si->openDatabase()) {
771
+ $code = $si->getCodeFromDatabase();
772
+
773
+ if (is_array($code)) {
774
+ $si->code = $code['code'];
775
+ $si->code_display = $code['code_disp'];
776
+ }
777
+
778
+ if ($si->check($value)) {
779
+ $si->clearCodeFromDatabase();
780
+
781
+ return true;
782
+ } else {
783
+ return false;
784
+ }
785
+ } else {
786
+ return false;
787
+ }
788
+ }
789
+
790
+
791
  /**
792
  * Used to serve a captcha image to the browser
793
  * @param string $background_image The path to the background image to use
794
+ * <code>
795
  * $img = new Securimage();
796
  * $img->code_length = 6;
797
  * $img->num_lines = 5;
798
  * $img->noise_level = 5;
799
+ *
800
  * $img->show(); // sends the image to browser
801
  * exit;
802
  * </code>
803
  */
804
  public function show($background_image = '')
805
  {
806
+ set_error_handler(array(&$this, 'errorHandler'));
807
+
808
  if($background_image != '' && is_readable($background_image)) {
809
  $this->bgimg = $background_image;
810
  }
811
 
812
  $this->doImage();
813
  }
814
+
815
  /**
816
  * Check a submitted code against the stored value
817
  * @param string $code The captcha code to check
831
  $this->validate();
832
  return $this->correct_code;
833
  }
834
+
835
  /**
836
  * Output a wav file of the captcha code to the browser
837
+ *
838
  * <code>
839
  * $img = new Securimage();
840
  * $img->outputAudioFile(); // outputs a wav file to the browser
843
  */
844
  public function outputAudioFile()
845
  {
846
+ set_error_handler(array(&$this, 'errorHandler'));
 
 
 
 
 
 
 
 
847
 
848
+ require_once dirname(__FILE__) . '/WavFile.php';
849
 
850
+ try {
851
+ $audio = $this->getAudibleCode();
852
+ } catch (Exception $ex) {
853
+ if (($fp = @fopen(dirname(__FILE__) . '/si.error_log', 'a+')) !== false) {
854
+ fwrite($fp, date('Y-m-d H:i:s') . ': Securimage audio error "' . $ex->getMessage() . '"' . "\n");
855
+ fclose($fp);
856
+ }
857
+
858
+ $audio = $this->audioError();
859
+ }
860
+
861
+ if ($this->canSendHeaders() || $this->send_headers == false) {
862
+ if ($this->send_headers) {
863
+ $uniq = md5(uniqid(microtime()));
864
+ header("Content-Disposition: attachment; filename=\"securimage_audio-{$uniq}.wav\"");
865
+ header('Cache-Control: no-store, no-cache, must-revalidate');
866
+ header('Expires: Sun, 1 Jan 2000 12:00:00 GMT');
867
+ header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
868
+ header('Content-type: audio/x-wav');
869
+
870
+ if (extension_loaded('zlib')) {
871
+ ini_set('zlib.output_compression', true); // compress output if supported by browser
872
+ } else {
873
+ header('Content-Length: ' . strlen($audio));
874
+ }
875
+ }
876
+
877
+ echo $audio;
878
+ } else {
879
+ echo '<hr /><strong>'
880
+ .'Failed to generate audio file, content has already been '
881
+ .'output.<br />This is most likely due to misconfiguration or '
882
+ .'a PHP error was sent to the browser.</strong>';
883
+ }
884
+
885
+ restore_error_handler();
886
+
887
+ if (!$this->no_exit) exit;
888
  }
889
+
890
+ /**
891
+ * Return the code from the session or sqlite database if used. If none exists yet, an empty string is returned
892
+ *
893
+ * @param $array bool True to receive an array containing the code and properties
894
+ * @return array|string Array if $array = true, otherwise a string containing the code
895
+ */
896
+ public function getCode($array = false, $returnExisting = false)
897
+ {
898
+ $code = '';
899
+ $time = 0;
900
+ $disp = 'error';
901
+
902
+ if ($returnExisting && strlen($this->code) > 0) {
903
+ if ($array) {
904
+ return array('code' => $this->code,
905
+ 'display' => $this->code_display,
906
+ 'code_display' => $this->code_display,
907
+ 'time' => 0);
908
+ } else {
909
+ return $this->code;
910
+ }
911
+ }
912
+
913
+ if ($this->no_session != true) {
914
+ if (isset($_SESSION['securimage_code_value'][$this->namespace]) &&
915
+ trim($_SESSION['securimage_code_value'][$this->namespace]) != '') {
916
+ if ($this->isCodeExpired(
917
+ $_SESSION['securimage_code_ctime'][$this->namespace]) == false) {
918
+ $code = $_SESSION['securimage_code_value'][$this->namespace];
919
+ $time = $_SESSION['securimage_code_ctime'][$this->namespace];
920
+ $disp = $_SESSION['securimage_code_disp'] [$this->namespace];
921
+ }
922
+ }
923
+ }
924
+
925
+ if (empty($code) && $this->use_database) {
926
+ // no code in session - may mean user has cookies turned off
927
+ $this->openDatabase();
928
+ $code = $this->getCodeFromDatabase();
929
+ } else { /* no code stored in session or sqlite database, validation will fail */ }
930
+
931
+ if ($array == true) {
932
+ return array('code' => $code, 'ctime' => $time, 'display' => $disp);
933
+ } else {
934
+ return $code;
935
+ }
936
+ }
937
+
938
  /**
939
  * The main image drawing routing, responsible for constructing the entire image and serving it
940
  */
945
  } else {
946
  $imagecreate = 'imagecreate';
947
  }
948
+
949
  $this->im = $imagecreate($this->image_width, $this->image_height);
950
  $this->tmpimg = $imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale);
951
+
952
  $this->allocateColors();
953
  imagepalettecopy($this->tmpimg, $this->im);
954
 
955
  $this->setBackground();
956
 
957
+ $code = '';
958
+
959
+ if ($this->getCaptchaId(false) !== null) {
960
+ // a captcha Id was supplied
961
+
962
+ // check to see if a display_value for the captcha image was set
963
+ if (is_string($this->display_value) && strlen($this->display_value) > 0) {
964
+ $this->code_display = $this->display_value;
965
+ $this->code = ($this->case_sensitive) ?
966
+ $this->display_value :
967
+ strtolower($this->display_value);
968
+ $code = $this->code;
969
+ } else if ($this->openDatabase()) {
970
+ // no display_value, check the database for existing captchaId
971
+ $code = $this->getCodeFromDatabase();
972
+
973
+ // got back a result from the database with a valid code for captchaId
974
+ if (is_array($code)) {
975
+ $this->code = $code['code'];
976
+ $this->code_display = $code['code_disp'];
977
+ $code = $code['code'];
978
+ }
979
+ }
980
+ }
981
+
982
+ if ($code == '') {
983
+ // if the code was not set using display_value or was not found in
984
+ // the database, create a new code
985
+ $this->createCode();
986
+ }
987
 
988
  if ($this->noise_level > 0) {
989
  $this->drawNoise();
990
  }
991
+
992
  $this->drawWord();
993
+
994
  if ($this->perturbation > 0 && is_readable($this->ttf_file)) {
995
  $this->distortedCopy();
996
  }
1005
 
1006
  $this->output();
1007
  }
1008
+
1009
  /**
1010
  * Allocate the colors to be used for the image
1011
  */
1016
  $this->image_bg_color->r,
1017
  $this->image_bg_color->g,
1018
  $this->image_bg_color->b);
1019
+
1020
  $alpha = intval($this->text_transparency_percentage / 100 * 127);
1021
+
1022
  if ($this->use_transparent_text == true) {
1023
  $this->gdtextcolor = imagecolorallocatealpha($this->im,
1024
  $this->text_color->r,
1049
  $this->noise_color->g,
1050
  $this->noise_color->b);
1051
  }
1052
+
1053
  $this->gdsignaturecolor = imagecolorallocate($this->im,
1054
  $this->signature_color->r,
1055
  $this->signature_color->g,
1056
  $this->signature_color->b);
1057
 
1058
  }
1059
+
1060
  /**
1061
  * The the background color, or background image to be used
1062
  */
1069
  imagefilledrectangle($this->tmpimg, 0, 0,
1070
  $this->image_width * $this->iscale, $this->image_height * $this->iscale,
1071
  $this->gdbgcolor);
1072
+
1073
  if ($this->bgimg == '') {
1074
+ if ($this->background_directory != null &&
1075
  is_dir($this->background_directory) &&
1076
  is_readable($this->background_directory))
1077
  {
1081
  }
1082
  }
1083
  }
1084
+
1085
  if ($this->bgimg == '') {
1086
  return;
1087
  }
1088
 
1089
  $dat = @getimagesize($this->bgimg);
1090
+ if($dat == false) {
1091
  return;
1092
  }
1093
 
1104
  $this->image_width, $this->image_height,
1105
  imagesx($newim), imagesy($newim));
1106
  }
1107
+
1108
  /**
1109
  * Scan the directory for a background image to use
1110
  */
1120
  closedir($dh);
1121
 
1122
  if (sizeof($images) > 0) {
1123
+ return rtrim($this->background_directory, '/') . '/' . $images[mt_rand(0, sizeof($images)-1)];
1124
  }
1125
  }
1126
 
1127
  return false;
1128
  }
1129
+
1130
  /**
1131
  * Generates the code or math problem and saves the value to the session
1132
  */
1133
+ public function createCode()
1134
  {
1135
  $this->code = false;
1136
 
1137
  switch($this->captcha_type) {
1138
  case self::SI_CAPTCHA_MATHEMATIC:
1139
  {
1140
+ do {
1141
+ $signs = array('+', '-', 'x');
1142
+ $left = mt_rand(1, 10);
1143
+ $right = mt_rand(1, 5);
1144
+ $sign = $signs[mt_rand(0, 2)];
1145
+
1146
+ switch($sign) {
1147
+ case 'x': $c = $left * $right; break;
1148
+ case '-': $c = $left - $right; break;
1149
+ default: $c = $left + $right; break;
1150
+ }
1151
+ } while ($c <= 0); // no negative #'s or 0
1152
+
1153
  $this->code = $c;
1154
  $this->code_display = "$left $sign $right";
1155
  break;
1156
  }
1157
 
1158
+ case self::SI_CAPTCHA_WORDS:
1159
+ $words = $this->readCodeFromFile(2);
1160
+ $this->code = implode(' ', $words);
1161
+ $this->code_display = $this->code;
1162
+ break;
1163
+
1164
  default:
1165
  {
1166
  if ($this->use_wordlist && is_readable($this->wordlist_file)) {
1170
  if ($this->code == false) {
1171
  $this->code = $this->generateCode($this->code_length);
1172
  }
1173
+
1174
  $this->code_display = $this->code;
1175
  $this->code = ($this->case_sensitive) ? $this->code : strtolower($this->code);
1176
  } // default
1177
  }
1178
+
1179
  $this->saveData();
1180
  }
1181
+
1182
  /**
1183
  * Draws the captcha code on the image
1184
  */
1186
  {
1187
  $width2 = $this->image_width * $this->iscale;
1188
  $height2 = $this->image_height * $this->iscale;
1189
+
1190
  if (!is_readable($this->ttf_file)) {
1191
  imagestring($this->im, 4, 10, ($this->image_height / 2) - 5, 'Failed to load TTF font file!', $this->gdtextcolor);
1192
  } else {
1210
  imagettftext($this->im, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
1211
  }
1212
  }
1213
+
1214
  // DEBUG
1215
  //$this->im = $this->tmpimg;
1216
  //$this->output();
1217
+
1218
  }
1219
+
1220
  /**
1221
  * Copies the captcha image to the final image with distortion applied
1222
  */
1225
  $numpoles = 3; // distortion factor
1226
  // make array of poles AKA attractor points
1227
  for ($i = 0; $i < $numpoles; ++ $i) {
1228
+ $px[$i] = mt_rand($this->image_width * 0.2, $this->image_width * 0.8);
1229
+ $py[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
1230
+ $rad[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
1231
  $tmp = ((- $this->frand()) * 0.15) - .15;
1232
  $amp[$i] = $this->perturbation * $tmp;
1233
  }
1234
+
1235
  $bgCol = imagecolorat($this->tmpimg, 0, 0);
1236
  $width2 = $this->iscale * $this->image_width;
1237
  $height2 = $this->iscale * $this->image_height;
1267
  }
1268
  }
1269
  }
1270
+
1271
  /**
1272
  * Draws distorted lines on the image
1273
  */
1276
  for ($line = 0; $line < $this->num_lines; ++ $line) {
1277
  $x = $this->image_width * (1 + $line) / ($this->num_lines + 1);
1278
  $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines;
1279
+ $y = mt_rand($this->image_height * 0.1, $this->image_height * 0.9);
1280
+
1281
  $theta = ($this->frand() - 0.5) * M_PI * 0.7;
1282
  $w = $this->image_width;
1283
+ $len = mt_rand($w * 0.4, $w * 0.7);
1284
+ $lwid = mt_rand(0, 2);
1285
+
1286
  $k = $this->frand() * 0.6 + 0.2;
1287
  $k = $k * $k * 0.5;
1288
  $phi = $this->frand() * 6.28;
1293
  $amp = 1.5 * $this->frand() / ($k + 5.0 / $len);
1294
  $x0 = $x - 0.5 * $len * cos($theta);
1295
  $y0 = $y - 0.5 * $len * sin($theta);
1296
+
1297
  $ldx = round(- $dy * $lwid);
1298
  $ldy = round($dx * $lwid);
1299
+
1300
  for ($i = 0; $i < $n; ++ $i) {
1301
  $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi);
1302
  $y = $y0 + $i * $dy - $amp * $dx * sin($k * $i * $step + $phi);
1304
  }
1305
  }
1306
  }
1307
+
1308
  /**
1309
  * Draws random noise on the image
1310
  */
1317
  }
1318
 
1319
  $t0 = microtime(true);
1320
+
1321
  $noise_level *= 125; // an arbitrary number that works well on a 1-10 scale
1322
+
1323
  $points = $this->image_width * $this->image_height * $this->iscale;
1324
  $height = $this->image_height * $this->iscale;
1325
  $width = $this->image_width * $this->iscale;
1326
  for ($i = 0; $i < $noise_level; ++$i) {
1327
+ $x = mt_rand(10, $width);
1328
+ $y = mt_rand(10, $height);
1329
+ $size = mt_rand(7, 10);
1330
  if ($x - $size <= 0 && $y - $size <= 0) continue; // dont cover 0,0 since it is used by imagedistortedcopy
1331
  imagefilledarc($this->tmpimg, $x, $y, $size, $size, 0, 360, $this->gdnoisecolor, IMG_ARC_PIE);
1332
  }
1333
+
1334
  $t1 = microtime(true);
1335
+
1336
  $t = $t1 - $t0;
1337
+
1338
  /*
1339
  // DEBUG
1340
  imagestring($this->tmpimg, 5, 25, 30, "$t", $this->gdnoisecolor);
1343
  exit;
1344
  */
1345
  }
1346
+
1347
  /**
1348
  * Print signature text on image
1349
  */
1350
  protected function addSignature()
1351
+ {
1352
  $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature);
1353
  $textlen = $bbox[2] - $bbox[0];
1354
  $x = $this->image_width - $textlen - 5;
1355
  $y = $this->image_height - 3;
1356
+
1357
+ imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature);
1358
  }
1359
+
1360
  /**
1361
  * Sends the appropriate image and cache headers and outputs image to the browser
1362
  */
1363
  protected function output()
1364
  {
1365
+ if ($this->canSendHeaders() || $this->send_headers == false) {
1366
+ if ($this->send_headers) {
1367
+ // only send the content-type headers if no headers have been output
1368
+ // this will ease debugging on misconfigured servers where warnings
1369
+ // may have been output which break the image and prevent easily viewing
1370
+ // source to see the error.
1371
+ header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
1372
+ header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
1373
+ header("Cache-Control: no-store, no-cache, must-revalidate");
1374
+ header("Cache-Control: post-check=0, pre-check=0", false);
1375
+ header("Pragma: no-cache");
1376
+ }
1377
+
1378
+ switch ($this->image_type) {
1379
+ case self::SI_IMAGE_JPEG:
1380
+ if ($this->send_headers) header("Content-Type: image/jpeg");
1381
+ imagejpeg($this->im, null, 90);
1382
+ break;
1383
+ case self::SI_IMAGE_GIF:
1384
+ if ($this->send_headers) header("Content-Type: image/gif");
1385
+ imagegif($this->im);
1386
+ break;
1387
+ default:
1388
+ if ($this->send_headers) header("Content-Type: image/png");
1389
+ imagepng($this->im);
1390
+ break;
1391
+ }
1392
+ } else {
1393
+ echo '<hr /><strong>'
1394
+ .'Failed to generate captcha image, content has already been '
1395
+ .'output.<br />This is most likely due to misconfiguration or '
1396
+ .'a PHP error was sent to the browser.</strong>';
1397
  }
1398
+
1399
  imagedestroy($this->im);
1400
+ restore_error_handler();
1401
+
1402
+ if (!$this->no_exit) exit;
1403
  }
1404
+
1405
  /**
1406
  * Gets the code and returns the binary audio file for the stored captcha code
1407
+ *
1408
+ * @return The audio representation of the captcha in Wav format
1409
  */
1410
+ protected function getAudibleCode()
1411
  {
 
 
 
 
1412
  $letters = array();
1413
+ $code = $this->getCode(true, true);
1414
 
1415
+ if ($code['code'] == '') {
1416
+ if (strlen($this->display_value) > 0) {
1417
+ $code = array('code' => $this->display_value, 'display' => $this->display_value);
1418
+ } else {
1419
+ $this->createCode();
1420
+ $code = $this->getCode(true);
1421
+ }
1422
  }
1423
 
1424
+ if (preg_match('/(\d+) (\+|-|x) (\d+)/i', $code['display'], $eq)) {
1425
+ $math = true;
1426
+
1427
+ $left = $eq[1];
1428
+ $sign = str_replace(array('+', '-', 'x'), array('plus', 'minus', 'times'), $eq[2]);
1429
+ $right = $eq[3];
1430
+
1431
+ $letters = array($left, $sign, $right);
1432
  } else {
1433
+ $math = false;
1434
+
1435
+ $length = strlen($code['display']);
1436
+
1437
+ for($i = 0; $i < $length; ++$i) {
1438
+ $letter = $code['display']{$i};
1439
+ $letters[] = $letter;
1440
+ }
1441
+ }
1442
+
1443
+ try {
1444
  return $this->generateWAV($letters);
1445
+ } catch(Exception $ex) {
1446
+ throw $ex;
1447
  }
1448
  }
1449
 
1450
  /**
1451
  * Gets a captcha code from a wordlist
1452
  */
1453
+ protected function readCodeFromFile($numWords = 1)
1454
  {
1455
+ $fp = fopen($this->wordlist_file, 'rb');
1456
  if (!$fp) return false;
1457
 
1458
  $fsize = filesize($this->wordlist_file);
1459
  if ($fsize < 128) return false; // too small of a list to be effective
1460
+
1461
+ if ((int)$numWords < 1 || (int)$numWords > 5) $numWords = 1;
1462
+
1463
+ $words = array();
1464
+ $i = 0;
1465
+ do {
1466
+ fseek($fp, mt_rand(0, $fsize - 64), SEEK_SET); // seek to a random position of file from 0 to filesize-64
1467
+ $data = fread($fp, 64); // read a chunk from our random position
1468
+ $data = preg_replace("/\r?\n/", "\n", $data);
1469
+
1470
+ $start = @strpos($data, "\n", mt_rand(0, 56)) + 1; // random start position
1471
+ $end = @strpos($data, "\n", $start); // find end of word
1472
+
1473
+ if ($start === false) {
1474
+ // picked start position at end of file
1475
+ continue;
1476
+ } else if ($end === false) {
1477
+ $end = strlen($data);
1478
+ }
1479
+
1480
+ $word = strtolower(substr($data, $start, $end - $start)); // return a line of the file
1481
+ $words[] = $word;
1482
+ } while (++$i < $numWords);
1483
+
1484
  fclose($fp);
 
 
 
 
1485
 
1486
+ if ($numWords < 2) {
1487
+ return $words[0];
1488
+ } else {
1489
+ return $words;
1490
  }
 
 
1491
  }
1492
+
1493
  /**
1494
  * Generates a random captcha code from the set character set
1495
  */
1497
  {
1498
  $code = '';
1499
 
1500
+ if (function_exists('mb_strlen')) {
1501
+ for($i = 1, $cslen = mb_strlen($this->charset); $i <= $this->code_length; ++$i) {
1502
+ $code .= mb_substr($this->charset, mt_rand(0, $cslen - 1), 1, 'UTF-8');
1503
+ }
1504
+ } else {
1505
+ for($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i) {
1506
+ $code .= substr($this->charset, mt_rand(0, $cslen - 1), 1);
1507
+ }
1508
  }
1509
+
 
 
1510
  return $code;
1511
  }
1512
+
1513
  /**
1514
  * Checks the entered code against the value stored in the session or sqlite database, handles case sensitivity
1515
  * Also clears the stored codes if the code was entered correctly to prevent re-use
1516
  */
1517
  protected function validate()
1518
  {
1519
+ if (!is_string($this->code) || strlen($this->code) == 0) {
1520
+ $code = $this->getCode();
1521
+ // returns stored code, or an empty string if no stored code was found
1522
+ // checks the session and database if enabled
1523
+ } else {
1524
+ $code = $this->code;
1525
+ }
1526
+
1527
  if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code)) {
1528
  // case sensitive was set from securimage_show.php but not in class
1529
  // the code saved in the session has capitals so set case sensitive to true
1530
  $this->case_sensitive = true;
1531
  }
1532
+
1533
  $code_entered = trim( (($this->case_sensitive) ? $this->code_entered
1534
  : strtolower($this->code_entered))
1535
  );
1536
  $this->correct_code = false;
1537
+
1538
  if ($code != '') {
1539
+ if (strpos($code, ' ') !== false) {
1540
+ // for multi word captchas, remove more than once space from input
1541
+ $code_entered = preg_replace('/\s+/', ' ', $code_entered);
1542
+ $code_entered = strtolower($code_entered);
1543
+ }
1544
+
1545
  if ($code == $code_entered) {
1546
  $this->correct_code = true;
1547
+ if ($this->no_session != true) {
1548
+ $_SESSION['securimage_code_value'][$this->namespace] = '';
1549
+ $_SESSION['securimage_code_ctime'][$this->namespace] = '';
1550
+ }
1551
  $this->clearCodeFromDatabase();
1552
  }
1553
  }
1554
  }
1555
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1556
  /**
1557
  * Save data to session namespace and database if used
1558
  */
1559
  protected function saveData()
1560
  {
1561
+ if ($this->no_session != true) {
1562
+ if (isset($_SESSION['securimage_code_value']) && is_scalar($_SESSION['securimage_code_value'])) {
1563
+ // fix for migration from v2 - v3
1564
+ unset($_SESSION['securimage_code_value']);
1565
+ unset($_SESSION['securimage_code_ctime']);
1566
+ }
1567
+
1568
+ $_SESSION['securimage_code_disp'] [$this->namespace] = $this->code_display;
1569
+ $_SESSION['securimage_code_value'][$this->namespace] = $this->code;
1570
+ $_SESSION['securimage_code_ctime'][$this->namespace] = time();
1571
+ }
1572
 
1573
+ if ($this->use_database) {
1574
+ $this->saveCodeToDatabase();
1575
+ }
1576
  }
1577
+
1578
  /**
1579
  * Saves the code to the sqlite database
1580
  */
1581
  protected function saveCodeToDatabase()
1582
  {
1583
  $success = false;
 
1584
  $this->openDatabase();
1585
+
1586
+ if ($this->use_database && $this->pdo_conn) {
1587
+ $id = $this->getCaptchaId(false);
1588
+ $ip = $_SERVER['REMOTE_ADDR'];
1589
+
1590
+ if (empty($id)) {
1591
+ $id = $ip;
1592
+ }
1593
+
1594
+ $time = time();
1595
+ $code = $this->code;
1596
+ $code_disp = $this->code_display;
1597
+
1598
+ // This is somewhat expensive in PDO Sqlite3 (when there is something to delete)
1599
+ $this->clearCodeFromDatabase();
1600
+
1601
+ $query = "INSERT INTO {$this->database_table} ("
1602
+ ."id, code, code_display, namespace, created) "
1603
+ ."VALUES(?, ?, ?, ?, ?)";
1604
+
1605
+ $stmt = $this->pdo_conn->prepare($query);
1606
+ $success = $stmt->execute(array($id, $code, $code_disp, $this->namespace, $time));
1607
+
1608
+ if (!$success) {
1609
+ $err = $stmt->errorInfo();
1610
+ trigger_error("Failed to insert code into database. {$err[1]}: {$err[2]}", E_USER_WARNING);
1611
+ }
1612
  }
1613
 
1614
  return $success !== false;
1615
  }
1616
+
1617
  /**
1618
  * Open sqlite database
1619
  */
1620
  protected function openDatabase()
1621
  {
1622
+ $this->pdo_conn = false;
1623
 
1624
+ if ($this->use_database) {
1625
+ $pdo_extension = 'PDO_' . strtoupper($this->database_driver);
1626
 
1627
+ if (!extension_loaded($pdo_extension)) {
1628
+ trigger_error("Database support is turned on in Securimage, but the chosen extension $pdo_extension is not loaded in PHP.", E_USER_WARNING);
1629
+ return false;
1630
+ }
1631
+ }
1632
+
1633
+ if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
1634
+ if (!file_exists($this->database_file)) {
1635
+ $fp = fopen($this->database_file, 'w+');
1636
+ if (!$fp) {
1637
+ $err = error_get_last();
1638
+ trigger_error("Securimage failed to create SQLite3 database file '{$this->database_file}'. Reason: {$err['message']}", E_USER_WARNING);
1639
+ return false;
1640
  }
1641
+ fclose($fp);
1642
+ chmod($this->database_file, 0666);
1643
+ } else if (!is_writeable($this->database_file)) {
1644
+ trigger_error("Securimage does not have read/write access to database file '{$this->database_file}. Make sure permissions are 0666 and writeable by user '" . get_current_user() . "'", E_USER_WARNING);
1645
+ return false;
1646
+ }
1647
+ }
1648
+
1649
+ $dsn = $this->getDsn();
1650
+
1651
+ try {
1652
+ $options = array();
1653
+ $this->pdo_conn = new PDO($dsn, $this->database_user, $this->database_pass, $options);
1654
+ } catch (PDOException $pdoex) {
1655
+ trigger_error("Database connection failed: " . $pdoex->getMessage(), E_USER_WARNING);
1656
+ return false;
1657
+ }
1658
+
1659
+ try {
1660
+ if (!$this->checkTablesExist()) {
1661
+ // create tables...
1662
+ $this->createDatabaseTables();
1663
+ }
1664
+ } catch (Exception $ex) {
1665
+ trigger_error($ex->getMessage(), E_USER_WARNING);
1666
+ $this->pdo_conn = null;
1667
+ return false;
1668
+ }
1669
+
1670
+ if (mt_rand(0, 100) / 100.0 == 1.0) {
1671
+ $this->purgeOldCodesFromDatabase();
1672
+ }
1673
+
1674
+ return $this->pdo_conn;
1675
+ }
1676
+
1677
+ protected function getDsn()
1678
+ {
1679
+ $dsn = sprintf('%s:', $this->database_driver);
1680
+
1681
+ switch($this->database_driver) {
1682
+ case self::SI_DRIVER_SQLITE3:
1683
+ $dsn .= $this->database_file;
1684
+ break;
1685
+
1686
+ case self::SI_DRIVER_MYSQL:
1687
+ case self::SI_DRIVER_PGSQL:
1688
+ $dsn .= sprintf('host=%s;dbname=%s',
1689
+ $this->database_host,
1690
+ $this->database_name);
1691
+ break;
1692
+
1693
+ }
1694
+
1695
+ return $dsn;
1696
+ }
1697
+
1698
+ protected function checkTablesExist()
1699
+ {
1700
+ $table = $this->pdo_conn->quote($this->database_table);
1701
+
1702
+ switch($this->database_driver) {
1703
+ case self::SI_DRIVER_SQLITE3:
1704
+ // query row count for sqlite, PRAGMA queries seem to return no
1705
+ // rowCount using PDO even if there are rows returned
1706
+ $query = "SELECT COUNT(id) FROM $table";
1707
+ break;
1708
+
1709
+ case self::SI_DRIVER_MYSQL:
1710
+ $query = "SHOW TABLES LIKE $table";
1711
+ break;
1712
+
1713
+ case self::SI_DRIVER_PGSQL:
1714
+ $query = "SELECT * FROM information_schema.columns WHERE table_name = $table;";
1715
+ break;
1716
+ }
1717
+
1718
+ $result = $this->pdo_conn->query($query);
1719
+
1720
+ if (!$result) {
1721
+ $err = $this->pdo_conn->errorInfo();
1722
+
1723
+ if ($this->database_driver == self::SI_DRIVER_SQLITE3 &&
1724
+ $err[1] === 1 && strpos($err[2], 'no such table') !== false)
1725
+ {
1726
+ return false;
1727
  }
1728
 
1729
+ throw new Exception("Failed to check tables: {$err[0]} - {$err[1]}: {$err[2]}");
1730
+ } else if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
1731
+ // successful here regardless of row count for sqlite
1732
+ return true;
1733
+ } else if ($result->rowCount() == 0) {
1734
+ return false;
1735
+ } else {
1736
+ return true;
1737
  }
1738
+ }
1739
+
1740
+ protected function createDatabaseTables()
1741
+ {
1742
+ $queries = array();
1743
 
1744
+ switch($this->database_driver) {
1745
+ case self::SI_DRIVER_SQLITE3:
1746
+ $queries[] = "CREATE TABLE \"{$this->database_table}\" (
1747
+ id VARCHAR(40),
1748
+ namespace VARCHAR(32) NOT NULL,
1749
+ code VARCHAR(32) NOT NULL,
1750
+ code_display VARCHAR(32) NOT NULL,
1751
+ created INTEGER NOT NULL,
1752
+ PRIMARY KEY(id, namespace)
1753
+ )";
1754
+
1755
+ $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created)";
1756
+ break;
1757
+
1758
+ case self::SI_DRIVER_MYSQL:
1759
+ $queries[] = "CREATE TABLE `{$this->database_table}` (
1760
+ `id` VARCHAR(40) NOT NULL,
1761
+ `namespace` VARCHAR(32) NOT NULL,
1762
+ `code` VARCHAR(32) NOT NULL,
1763
+ `code_display` VARCHAR(32) NOT NULL,
1764
+ `created` INT NOT NULL,
1765
+ PRIMARY KEY(id, namespace),
1766
+ INDEX(created)
1767
+ )";
1768
+ break;
1769
+
1770
+ case self::SI_DRIVER_PGSQL:
1771
+ $queries[] = "CREATE TABLE {$this->database_table} (
1772
+ id character varying(40) NOT NULL,
1773
+ namespace character varying(32) NOT NULL,
1774
+ code character varying(32) NOT NULL,
1775
+ code_display character varying(32) NOT NULL,
1776
+ created integer NOT NULL,
1777
+ CONSTRAINT pkey_id_namespace PRIMARY KEY (id, namespace)
1778
+ )";
1779
+
1780
+ $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created);";
1781
+ break;
1782
+ }
1783
+
1784
+ $this->pdo_conn->beginTransaction();
1785
+
1786
+ foreach($queries as $query) {
1787
+ $result = $this->pdo_conn->query($query);
1788
+
1789
+ if (!$result) {
1790
+ $err = $this->pdo_conn->errorInfo();
1791
+ trigger_error("Failed to create table. {$err[1]}: {$err[2]}", E_USER_WARNING);
1792
+ $this->pdo_conn->rollBack();
1793
+ $this->pdo_conn = false;
1794
+ return false;
1795
+ }
1796
+ }
1797
+
1798
+ $this->pdo_conn->commit();
1799
+
1800
+ return true;
1801
  }
1802
 
1803
  /**
1804
+ * Get a code from the sqlite database for ip address/captchaId.
1805
+ *
1806
+ * @return string|array Empty string if no code was found or has expired,
1807
+ * otherwise returns the stored captcha code. If a captchaId is set, this
1808
+ * returns an array with indices "code" and "code_disp"
1809
  */
1810
  protected function getCodeFromDatabase()
1811
  {
1812
  $code = '';
1813
 
1814
+ if ($this->use_database == true && $this->pdo_conn) {
1815
+ if (Securimage::$_captchaId !== null) {
1816
+ $query = "SELECT * FROM {$this->database_table} WHERE id = ?";
1817
+ $stmt = $this->pdo_conn->prepare($query);
1818
+ $result = $stmt->execute(array(Securimage::$_captchaId));
1819
+ } else {
1820
+ $ip = $_SERVER['REMOTE_ADDR'];
1821
+ $ns = $this->namespace;
1822
+
1823
+ // ip is stored in id column when no captchaId
1824
+ $query = "SELECT * FROM {$this->database_table} WHERE id = ? AND namespace = ?";
1825
+ $stmt = $this->pdo_conn->prepare($query);
1826
+ $result = $stmt->execute(array($ip, $ns));
1827
+ }
1828
+
1829
+ if (!$result) {
1830
+ $err = $this->pdo_conn->errorInfo();
1831
+ trigger_error("Failed to select code from database. {$err[0]}: {$err[1]}", E_USER_WARNING);
1832
+ } else {
1833
+ if ( ($row = $stmt->fetch()) !== false ) {
1834
+ if (false == $this->isCodeExpired($row['created'])) {
1835
+ if (Securimage::$_captchaId !== null) {
1836
+ // return an array when using captchaId
1837
+ $code = array('code' => $row['code'],
1838
+ 'code_disp' => $row['code_display']);
1839
+ } else {
1840
+ $code = $row['code'];
1841
+ }
1842
+ }
1843
  }
1844
  }
1845
  }
1846
+
1847
  return $code;
1848
  }
1849
+
1850
  /**
1851
  * Remove an entered code from the database
1852
  */
1853
  protected function clearCodeFromDatabase()
1854
  {
1855
+ if ($this->pdo_conn) {
1856
  $ip = $_SERVER['REMOTE_ADDR'];
1857
+ $ns = $this->pdo_conn->quote($this->namespace);
1858
+ $id = Securimage::$_captchaId;
1859
+
1860
+ if (empty($id)) {
1861
+ $id = $ip; // if no captchaId set, IP address is captchaId.
1862
+ }
1863
+
1864
+ $id = $this->pdo_conn->quote($id);
1865
 
1866
+ $query = sprintf("DELETE FROM %s WHERE id = %s AND namespace = %s",
1867
+ $this->database_table, $id, $ns);
1868
+
1869
+ $result = $this->pdo_conn->query($query);
1870
+ if (!$result) {
1871
+ trigger_error("Failed to delete code from database.", E_USER_WARNING);
1872
+ }
1873
  }
1874
  }
1875
+
1876
  /**
1877
  * Deletes old codes from sqlite database
1878
  */
1879
  protected function purgeOldCodesFromDatabase()
1880
  {
1881
+ if ($this->use_database && $this->pdo_conn) {
1882
  $now = time();
1883
  $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time;
1884
 
1885
+ $query = sprintf("DELETE FROM %s WHERE %s - created > %s",
1886
+ $this->database_table,
1887
+ $this->pdo_conn->quote($now, PDO::PARAM_INT),
1888
+ $this->pdo_conn->quote($limit, PDO::PARAM_INT));
1889
+
1890
+ $result = $this->pdo_conn->query($query);
1891
  }
1892
  }
1893
+
1894
  /**
1895
  * Checks to see if the captcha code has expired and cannot be used
1896
  * @param unknown_type $creation_time
1898
  protected function isCodeExpired($creation_time)
1899
  {
1900
  $expired = true;
1901
+
1902
  if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) {
1903
  $expired = false;
1904
  } else if (time() - $creation_time < $this->expiry_time) {
1905
  $expired = false;
1906
  }
1907
+
1908
  return $expired;
1909
  }
1910
+
 
 
 
 
 
 
 
 
 
 
 
1911
  /**
1912
  * Generate a wav file given the $letters in the code
1913
  * @todo Add ability to merge 2 sound files together to have random background sounds
1916
  */
1917
  protected function generateWAV($letters)
1918
  {
1919
+ $wavCaptcha = new WavFile();
1920
+ $first = true; // reading first wav file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1921
 
1922
+ foreach ($letters as $letter) {
1923
+ $letter = strtoupper($letter);
1924
+
1925
+ try {
1926
+ $l = new WavFile($this->audio_path . '/' . $letter . '.wav');
1927
+
1928
+ if ($first) {
1929
+ // set sample rate, bits/sample, and # of channels for file based on first letter
1930
+ $wavCaptcha->setSampleRate($l->getSampleRate())
1931
+ ->setBitsPerSample($l->getBitsPerSample())
1932
+ ->setNumChannels($l->getNumChannels());
1933
+ $first = false;
1934
+ }
1935
+
1936
+ // append letter to the captcha audio
1937
+ $wavCaptcha->appendWav($l);
1938
+
1939
+ // random length of silence between $audio_gap_min and $audio_gap_max
1940
+ if ($this->audio_gap_max > 0 && $this->audio_gap_max > $this->audio_gap_min) {
1941
+ $wavCaptcha->insertSilence( mt_rand($this->audio_gap_min, $this->audio_gap_max) / 1000.0 );
1942
+ }
1943
+ } catch (Exception $ex) {
1944
+ // failed to open file, or the wav file is broken or not supported
1945
+ // 2 wav files were not compatible, different # channels, bits/sample, or sample rate
1946
+ throw $ex;
 
 
 
 
 
 
 
 
 
 
 
1947
  }
1948
+ }
1949
+
1950
+ /********* Set up audio filters *****************************/
1951
+ $filters = array();
1952
+
1953
+ if ($this->audio_use_noise == true) {
1954
+ // use background audio - find random file
1955
+ $noiseFile = $this->getRandomNoiseFile();
1956
+
1957
+ if ($noiseFile !== false && is_readable($noiseFile)) {
1958
+ try {
1959
+ $wavNoise = new WavFile($noiseFile, false);
1960
+ } catch(Exception $ex) {
1961
+ throw $ex;
1962
+ }
1963
+
1964
+ // start at a random offset from the beginning of the wavfile
1965
+ // in order to add more randomness
1966
+ $randOffset = 0;
1967
+ if ($wavNoise->getNumBlocks() > 2 * $wavCaptcha->getNumBlocks()) {
1968
+ $randBlock = mt_rand(0, $wavNoise->getNumBlocks() - $wavCaptcha->getNumBlocks());
1969
+ $wavNoise->readWavData($randBlock * $wavNoise->getBlockAlign(), $wavCaptcha->getNumBlocks() * $wavNoise->getBlockAlign());
1970
+ } else {
1971
+ $wavNoise->readWavData();
1972
+ $randOffset = mt_rand(0, $wavNoise->getNumBlocks() - 1);
1973
  }
1974
+
1975
+
1976
+ $mixOpts = array('wav' => $wavNoise,
1977
+ 'loop' => true,
1978
+ 'blockOffset' => $randOffset);
1979
+
1980
+ $filters[WavFile::FILTER_MIX] = $mixOpts;
1981
+ $filters[WavFile::FILTER_NORMALIZE] = $this->audio_mix_normalization;
1982
  }
 
 
 
 
 
1983
  }
1984
 
1985
+ if ($this->degrade_audio == true) {
1986
+ // add random noise.
1987
+ // any noise level below 95% is intensely distorted and not pleasant to the ear
1988
+ $filters[WavFile::FILTER_DEGRADE] = mt_rand(95, 98) / 100.0;
1989
+ }
 
1990
 
1991
+ if (!empty($filters)) {
1992
+ $wavCaptcha->filter($filters); // apply filters to captcha audio
1993
+ }
1994
+
1995
+ return $wavCaptcha->__toString();
1996
  }
1997
+
1998
+ public function getRandomNoiseFile()
 
 
 
 
 
1999
  {
2000
+ $return = false;
2001
+
2002
+ if ( ($dh = opendir($this->audio_noise_path)) !== false ) {
2003
+ $list = array();
2004
+
2005
+ while ( ($file = readdir($dh)) !== false ) {
2006
+ if ($file == '.' || $file == '..') continue;
2007
+ if (strtolower(substr($file, -4)) != '.wav') continue;
2008
+
2009
+ $list[] = $file;
 
 
 
 
 
2010
  }
 
 
2011
 
2012
+ closedir($dh);
2013
+
2014
+ if (sizeof($list) > 0) {
2015
+ $file = $list[array_rand($list, 1)];
2016
+ $return = $this->audio_noise_path . DIRECTORY_SEPARATOR . $file;
2017
+ }
2018
  }
2019
 
2020
+ return $return;
2021
  }
2022
+
2023
  /**
2024
  * Return a wav file saying there was an error generating file
2025
+ *
2026
  * @return string The binary audio contents
2027
  */
2028
  protected function audioError()
2029
  {
2030
+ return @file_get_contents(dirname(__FILE__) . '/audio/en/error.wav');
2031
  }
2032
+
2033
+ /**
2034
+ * Checks to see if headers can be sent and if any error has been output to the browser
2035
+ *
2036
+ * @return bool true if headers haven't been sent and no output/errors will break audio/images, false if unsafe
2037
+ */
2038
+ protected function canSendHeaders()
2039
+ {
2040
+ if (headers_sent()) {
2041
+ // output has been flushed and headers have already been sent
2042
+ return false;
2043
+ } else if (strlen((string)ob_get_contents()) > 0) {
2044
+ // headers haven't been sent, but there is data in the buffer that will break image and audio data
2045
+ return false;
2046
+ }
2047
+
2048
+ return true;
2049
+ }
2050
+
2051
+ /**
2052
+ * Return a random float between 0 and 0.9999
2053
+ *
2054
+ * @return float Random float between 0 and 0.9999
2055
+ */
2056
  function frand()
2057
  {
2058
+ return 0.0001 * mt_rand(0,9999);
2059
  }
2060
+
2061
  /**
2062
  * Convert an html color code to a Securimage_Color
2063
  * @param string $color
2079
  return new Securimage_Color($default);
2080
  }
2081
  }
2082
+
2083
+ /**
2084
+ * Error handler used when outputting captcha image or audio.
2085
+ * This error handler helps determine if any errors raised would
2086
+ * prevent captcha image or audio from displaying. If they have
2087
+ * no effect on the output buffer or headers, true is returned so
2088
+ * the script can continue processing.
2089
+ * See https://github.com/dapphp/securimage/issues/15
2090
+ *
2091
+ * @param int $errno
2092
+ * @param string $errstr
2093
+ * @param string $errfile
2094
+ * @param int $errline
2095
+ * @param array $errcontext
2096
+ * @return boolean true if handled, false if PHP should handle
2097
+ */
2098
+ public function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array())
2099
+ {
2100
+ // get the current error reporting level
2101
+ $level = error_reporting();
2102
+
2103
+ // if error was supressed or $errno not set in current error level
2104
+ if ($level == 0 || ($level & $errno) == 0) {
2105
+ return true;
2106
+ }
2107
+
2108
+ return false;
2109
+ }
2110
  }
2111
 
2112
 
2132
  * when passing 3 arguments, specify each RGB component (from 0-255) individually.<br />
2133
  * $color = new Securimage_Color('#0080FF') or <br />
2134
  * $color = new Securimage_Color(0, 128, 255)
2135
+ *
2136
  * @param string $color
2137
  * @throws Exception
2138
  */
2139
  public function __construct($color = '#ffffff')
2140
  {
2141
  $args = func_get_args();
2142
+
2143
  if (sizeof($args) == 0) {
2144
  $this->r = 255;
2145
  $this->g = 255;
2149
  if (substr($color, 0, 1) == '#') {
2150
  $color = substr($color, 1);
2151
  }
2152
+
2153
  if (strlen($color) != 3 && strlen($color) != 6) {
2154
  throw new InvalidArgumentException(
2155
  'Invalid HTML color code passed to Securimage_Color'
2156
  );
2157
  }
2158
+
2159
  $this->constructHTML($color);
2160
  } else if (sizeof($args) == 3) {
2161
  $this->constructRGB($args[0], $args[1], $args[2]);
2165
  );
2166
  }
2167
  }
2168
+
2169
  /**
2170
  * Construct from an rgb triplet
2171
  * @param int $red The red component, 0-255
2180
  if ($green > 255) $green = 255;
2181
  if ($blue < 0) $blue = 0;
2182
  if ($blue > 255) $blue = 255;
2183
+
2184
  $this->r = $red;
2185
  $this->g = $green;
2186
  $this->b = $blue;
2187
  }
2188
+
2189
  /**
2190
  * Construct from an html hex color code
2191
  * @param string $color
2199
  } else {
2200
  $red = substr($color, 0, 2);
2201
  $green = substr($color, 2, 2);
2202
+ $blue = substr($color, 4, 2);
2203
  }
2204
+
2205
  $this->r = hexdec($red);
2206
  $this->g = hexdec($green);
2207
  $this->b = hexdec($blue);
securimage/securimage_play.php CHANGED
@@ -27,9 +27,9 @@
27
  * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
28
  * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
29
  * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
30
- * @copyright 2011 Drew Phillips
31
  * @author Drew Phillips <drew@drew-phillips.com>
32
- * @version 3.0 (October 2011)
33
  * @package Securimage
34
  *
35
  */
27
  * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
28
  * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
29
  * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
30
+ * @copyright 2012 Drew Phillips
31
  * @author Drew Phillips <drew@drew-phillips.com>
32
+ * @version 3.2RC2 (April 2012)
33
  * @package Securimage
34
  *
35
  */
securimage/securimage_play.swf CHANGED
Binary file
securimage/securimage_show.php CHANGED
@@ -4,18 +4,18 @@
4
  * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br />
5
  * File: securimage_show.php<br />
6
  *
7
- * Copyright (c) 2011, Drew Phillips
8
  * All rights reserved.
9
- *
10
  * Redistribution and use in source and binary forms, with or without modification,
11
  * are permitted provided that the following conditions are met:
12
- *
13
  * - Redistributions of source code must retain the above copyright notice,
14
  * this list of conditions and the following disclaimer.
15
  * - Redistributions in binary form must reproduce the above copyright notice,
16
  * this list of conditions and the following disclaimer in the documentation
17
  * and/or other materials provided with the distribution.
18
- *
19
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -37,9 +37,9 @@
37
  * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
38
  * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
39
  * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
40
- * @copyright 2011 Drew Phillips
41
  * @author Drew Phillips <drew@drew-phillips.com>
42
- * @version 3.0 (October 2011)
43
  * @package Securimage
44
  *
45
  */
@@ -49,15 +49,15 @@
49
 
50
  require_once dirname(__FILE__) . '/securimage.php';
51
 
52
- $img = new securimage();
53
 
54
  // You can customize the image by making changes below, some examples are included - remove the "//" to uncomment
55
 
56
  //$img->ttf_file = './Quiff.ttf';
57
  //$img->captcha_type = Securimage::SI_CAPTCHA_MATHEMATIC; // show a simple math problem instead of text
58
  //$img->case_sensitive = true; // true to use case sensitve codes - not recommended
59
- //$img->image_height = 90; // width in pixels of the image
60
- //$img->image_width = $img->image_height * M_E; // a good formula for image size
61
  //$img->perturbation = .75; // 1.0 = high distortion, higher numbers = more distortion
62
  //$img->image_bg_color = new Securimage_Color("#0099CC"); // image background color
63
  //$img->text_color = new Securimage_Color("#EAEAEA"); // captcha text color
@@ -73,5 +73,5 @@ $img = new securimage();
73
 
74
 
75
  $img->show(); // outputs the image and content headers to the browser
76
- // alternate use:
77
  // $img->show('/path/to/background_image.jpg');
4
  * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br />
5
  * File: securimage_show.php<br />
6
  *
7
+ * Copyright (c) 2013, Drew Phillips
8
  * All rights reserved.
9
+ *
10
  * Redistribution and use in source and binary forms, with or without modification,
11
  * are permitted provided that the following conditions are met:
12
+ *
13
  * - Redistributions of source code must retain the above copyright notice,
14
  * this list of conditions and the following disclaimer.
15
  * - Redistributions in binary form must reproduce the above copyright notice,
16
  * this list of conditions and the following disclaimer in the documentation
17
  * and/or other materials provided with the distribution.
18
+ *
19
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
37
  * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
38
  * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
39
  * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
40
+ * @copyright 2013 Drew Phillips
41
  * @author Drew Phillips <drew@drew-phillips.com>
42
+ * @version 3.5 (April 2013)
43
  * @package Securimage
44
  *
45
  */
49
 
50
  require_once dirname(__FILE__) . '/securimage.php';
51
 
52
+ $img = new Securimage();
53
 
54
  // You can customize the image by making changes below, some examples are included - remove the "//" to uncomment
55
 
56
  //$img->ttf_file = './Quiff.ttf';
57
  //$img->captcha_type = Securimage::SI_CAPTCHA_MATHEMATIC; // show a simple math problem instead of text
58
  //$img->case_sensitive = true; // true to use case sensitve codes - not recommended
59
+ //$img->image_height = 90; // height in pixels of the image
60
+ //$img->image_width = $img->image_height * M_E; // a good formula for image size based on the height
61
  //$img->perturbation = .75; // 1.0 = high distortion, higher numbers = more distortion
62
  //$img->image_bg_color = new Securimage_Color("#0099CC"); // image background color
63
  //$img->text_color = new Securimage_Color("#EAEAEA"); // captcha text color
73
 
74
 
75
  $img->show(); // outputs the image and content headers to the browser
76
+ // alternate use:
77
  // $img->show('/path/to/background_image.jpg');
securimage/securimage_show_example.php DELETED
@@ -1,65 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br />
5
- * File: securimage_show_example.php<br />
6
- *
7
- * Copyright (c) 2011, Drew Phillips
8
- * All rights reserved.
9
- *
10
- * Redistribution and use in source and binary forms, with or without modification,
11
- * are permitted provided that the following conditions are met:
12
- *
13
- * - Redistributions of source code must retain the above copyright notice,
14
- * this list of conditions and the following disclaimer.
15
- * - Redistributions in binary form must reproduce the above copyright notice,
16
- * this list of conditions and the following disclaimer in the documentation
17
- * and/or other materials provided with the distribution.
18
- *
19
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- * POSSIBILITY OF SUCH DAMAGE.
30
- *
31
- * Any modifications to the library should be indicated clearly in the source code
32
- * to inform users that the changes are not a part of the original software.<br /><br />
33
- *
34
- * If you found this script useful, please take a quick moment to rate it.<br />
35
- * http://www.hotscripts.com/rate/49400.html Thanks.
36
- *
37
- * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
38
- * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
39
- * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
40
- * @copyright 2011 Drew Phillips
41
- * @author Drew Phillips <drew@drew-phillips.com>
42
- * @version 3.0 (October 2011)
43
- * @package Securimage
44
- *
45
- */
46
-
47
- require_once dirname(__FILE__) . '/securimage.php';
48
-
49
- $img = new Securimage();
50
-
51
- //Change some settings
52
- $img->image_width = 250;
53
- $img->image_height = 80;
54
- $img->perturbation = 0.85;
55
- $img->image_bg_color = new Securimage_Color("#f6f6f6");
56
- $img->use_transparent_text = true;
57
- $img->text_transparency_percentage = 30; // 100 = completely transparent
58
- $img->num_lines = 7;
59
- $img->line_color = new Securimage_Color("#eaeaea");
60
- $img->image_signature = 'phpcaptcha.org';
61
- $img->signature_color = new Securimage_Color(rand(0, 64), rand(64, 128), rand(128, 255));
62
- $img->use_wordlist = true;
63
-
64
- $img->show('backgrounds/bg3.jpg'); // alternate use: $img->show('/path/to/background_image.jpg');
65
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
securimage/securimage_show_example2.php DELETED
@@ -1,63 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br />
5
- * File: securimage_show_example2.php<br />
6
- *
7
- * Copyright (c) 2011, Drew Phillips
8
- * All rights reserved.
9
- *
10
- * Redistribution and use in source and binary forms, with or without modification,
11
- * are permitted provided that the following conditions are met:
12
- *
13
- * - Redistributions of source code must retain the above copyright notice,
14
- * this list of conditions and the following disclaimer.
15
- * - Redistributions in binary form must reproduce the above copyright notice,
16
- * this list of conditions and the following disclaimer in the documentation
17
- * and/or other materials provided with the distribution.
18
- *
19
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- * POSSIBILITY OF SUCH DAMAGE.
30
- *
31
- * Any modifications to the library should be indicated clearly in the source code
32
- * to inform users that the changes are not a part of the original software.<br /><br />
33
- *
34
- * If you found this script useful, please take a quick moment to rate it.<br />
35
- * http://www.hotscripts.com/rate/49400.html Thanks.
36
- *
37
- * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
38
- * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
39
- * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
40
- * @copyright 2011 Drew Phillips
41
- * @author Drew Phillips <drew@drew-phillips.com>
42
- * @version 3.0 (October 2011)
43
- * @package Securimage
44
- *
45
- */
46
-
47
- require_once dirname(__FILE__) . '/securimage.php';
48
-
49
- $img = new Securimage();
50
-
51
- //Change some settings
52
- $img->image_width = 280;
53
- $img->image_height = 100;
54
- $img->perturbation = 0.9; // high level of distortion
55
- $img->code_length = rand(5,6); // random code length
56
- $img->image_bg_color = new Securimage_Color("#ffffff");
57
- $img->num_lines = 12;
58
- $img->noise_level = 5;
59
- $img->text_color = new Securimage_Color("#000000");
60
- $img->noise_color = $img->text_color;
61
- $img->line_color = new Securimage_Color("#cccccc");
62
-
63
- $img->show();