Version Description
Download this release
Release Info
Developer | Cimmo |
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
- README_OFFICIAL.txt +5 -0
- cimy_uef_register.php +3 -3
- cimy_user_extra_fields.php +5 -4
- css/cimy_uef_register.css +5 -1
- readme.txt +5 -1
- securimage/README.txt +76 -3
- securimage/WavFile.php +1864 -0
- securimage/audio/0.wav +0 -0
- securimage/audio/1.wav +0 -0
- securimage/audio/2.wav +0 -0
- securimage/audio/3.wav +0 -0
- securimage/audio/4.wav +0 -0
- securimage/audio/5.wav +0 -0
- securimage/audio/6.wav +0 -0
- securimage/audio/7.wav +0 -0
- securimage/audio/8.wav +0 -0
- securimage/audio/9.wav +0 -0
- securimage/audio/A.wav +0 -0
- securimage/audio/B.wav +0 -0
- securimage/audio/C.wav +0 -0
- securimage/audio/D.wav +0 -0
- securimage/audio/E.wav +0 -0
- securimage/audio/F.wav +0 -0
- securimage/audio/G.wav +0 -0
- securimage/audio/H.wav +0 -0
- securimage/audio/I.wav +0 -0
- securimage/audio/J.wav +0 -0
- securimage/audio/K.wav +0 -0
- securimage/audio/L.wav +0 -0
- securimage/audio/M.wav +0 -0
- securimage/audio/N.wav +0 -0
- securimage/audio/O.wav +0 -0
- securimage/audio/P.wav +0 -0
- securimage/audio/Q.wav +0 -0
- securimage/audio/R.wav +0 -0
- securimage/audio/S.wav +0 -0
- securimage/audio/T.wav +0 -0
- securimage/audio/U.wav +0 -0
- securimage/audio/V.wav +0 -0
- securimage/audio/W.wav +0 -0
- securimage/audio/X.wav +0 -0
- securimage/audio/Y.wav +0 -0
- securimage/audio/Z.wav +0 -0
- securimage/audio/en/0.wav +0 -0
- securimage/audio/en/1.wav +0 -0
- securimage/audio/en/10.wav +0 -0
- securimage/audio/en/11.wav +0 -0
- securimage/audio/en/12.wav +0 -0
- securimage/audio/en/13.wav +0 -0
- securimage/audio/en/14.wav +0 -0
- securimage/audio/en/15.wav +0 -0
- securimage/audio/en/16.wav +0 -0
- securimage/audio/en/17.wav +0 -0
- securimage/audio/en/18.wav +0 -0
- securimage/audio/en/19.wav +0 -0
- securimage/audio/en/2.wav +0 -0
- securimage/audio/en/20.wav +0 -0
- securimage/audio/en/3.wav +0 -0
- securimage/audio/en/4.wav +0 -0
- securimage/audio/en/5.wav +0 -0
- securimage/audio/en/6.wav +0 -0
- securimage/audio/en/7.wav +0 -0
- securimage/audio/en/8.wav +0 -0
- securimage/audio/en/9.wav +0 -0
- securimage/audio/en/A.wav +0 -0
- securimage/audio/en/B.wav +0 -0
- securimage/audio/en/C.wav +0 -0
- securimage/audio/en/D.wav +0 -0
- securimage/audio/en/E.wav +0 -0
- securimage/audio/en/F.wav +0 -0
- securimage/audio/en/G.wav +0 -0
- securimage/audio/en/H.wav +0 -0
- securimage/audio/en/I.wav +0 -0
- securimage/audio/en/J.wav +0 -0
- securimage/audio/en/K.wav +0 -0
- securimage/audio/en/L.wav +0 -0
- securimage/audio/en/M.wav +0 -0
- securimage/audio/en/MINUS.wav +0 -0
- securimage/audio/en/N.wav +0 -0
- securimage/audio/en/O.wav +0 -0
- securimage/audio/en/P.wav +0 -0
- securimage/audio/en/PLUS.wav +0 -0
- securimage/audio/en/Q.wav +0 -0
- securimage/audio/en/R.wav +0 -0
- securimage/audio/en/S.wav +0 -0
- securimage/audio/en/T.wav +0 -0
- securimage/audio/en/TIMES.wav +0 -0
- securimage/audio/en/U.wav +0 -0
- securimage/audio/en/V.wav +0 -0
- securimage/audio/en/W.wav +0 -0
- securimage/audio/en/X.wav +0 -0
- securimage/audio/en/Y.wav +0 -0
- securimage/audio/en/Z.wav +0 -0
- securimage/audio/{error.wav → en/error.wav} +0 -0
- securimage/captcha.html +3 -3
- securimage/database/securimage.sq3 +0 -0
- securimage/database/securimage.sqlite +0 -0
- securimage/example_form.ajax.php +17 -16
- securimage/example_form.php +17 -16
- securimage/images/audio_icon.gif +0 -0
- securimage/images/audio_icon.png +0 -0
- securimage/images/refresh.gif +0 -0
- securimage/images/refresh.png +0 -0
- securimage/securimage.php +1188 -416
- securimage/securimage_play.php +2 -2
- securimage/securimage_play.swf +0 -0
- securimage/securimage_show.php +10 -10
- securimage/securimage_show_example.php +0 -65
- 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&bgColor1=#fff&bgColor2=#fff&iconColor=#777&borderWidth=1&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&bgColor1=#fff&bgColor2=#fff&iconColor=#777&borderWidth=1&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.
|
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); ?> <input type="text" name="securimage_response_field" size="
|
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&bgColor1=#fff&bgColor2=#fff&iconColor=#777&borderWidth=1&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&bgColor1=#fff&bgColor2=#fff&iconColor=#777&borderWidth=1&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); ?> <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.
|
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.
|
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 |
-
|
|
|
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.
|
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.
|
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)
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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?
|
6 |
-
<param name="movie" value="./securimage_play.swf?
|
7 |
</object>
|
8 |
|
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="
|
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&icon_file=./images/audio_icon.png&audio_file=./securimage_play.php" height="32" width="32">
|
6 |
+
<param name="movie" value="./securimage_play.swf?bgcol=#ffffff&icon_file=./images/audio_icon.png&audio_file=./securimage_play.php" />
|
7 |
</object>
|
8 |
|
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
|
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"
|
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?
|
105 |
-
<param name="movie" value="./securimage_play.swf?
|
106 |
</object>
|
107 |
|
108 |
-
<a tabindex="-1" style="border-style: none;" href="#" title="Refresh Image" onclick="
|
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&icon_file=./images/audio_icon.png&audio_file=./securimage_play.php" height="32" width="32">
|
106 |
+
<param name="movie" value="./securimage_play.swf?bgcol=#ffffff&icon_file=./images/audio_icon.png&audio_file=./securimage_play.php" />
|
107 |
</object>
|
108 |
|
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
|
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
|
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> <?php echo @$_SESSION['ctform']['message_error'] ?><br />
|
66 |
-
<textarea name="ct_message"
|
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?
|
72 |
-
<param name="movie" value="./securimage_play.swf?
|
73 |
</object>
|
74 |
|
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"
|
76 |
<strong>Enter Code*:</strong><br />
|
77 |
<?php echo @$_SESSION['ctform']['captcha_error'] ?>
|
78 |
-
<input type="text" name="ct_captcha" size="12" maxlength="
|
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> <?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&icon_file=./images/audio_icon.png&audio_file=./securimage_play.php" height="32" width="32">
|
73 |
+
<param name="movie" value="./securimage_play.swf?bgcol=#ffffff&icon_file=./images/audio_icon.png&audio_file=./securimage_play.php" />
|
74 |
</object>
|
75 |
|
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)
|
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
|
43 |
* @author Drew Phillips <drew@drew-phillips.com>
|
44 |
-
* @version 3.
|
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.
|
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 =
|
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 =
|
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.
|
242 |
/**
|
243 |
* How many lines to draw over the captcha code to increase security
|
244 |
* @var int
|
245 |
*/
|
246 |
-
public $num_lines =
|
247 |
/**
|
248 |
* The level of noise (random dots) to place on the image, 0-10
|
249 |
* @var int
|
250 |
*/
|
251 |
-
public $noise_level =
|
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 |
-
|
336 |
-
|
|
|
|
|
|
|
|
|
|
|
337 |
protected $code;
|
|
|
|
|
|
|
|
|
|
|
|
|
338 |
protected $code_display;
|
339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
340 |
protected $captcha_code;
|
341 |
-
|
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 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
381 |
$this->ttf_file = $this->securimage_path . '/AHGBold.ttf';
|
382 |
}
|
383 |
-
|
384 |
$this->signature_font = $this->ttf_file;
|
385 |
-
|
386 |
-
if ($this->wordlist_file
|
387 |
$this->wordlist_file = $this->securimage_path . '/words/words.txt';
|
388 |
}
|
389 |
-
|
390 |
-
if ($this->
|
391 |
-
$this->
|
392 |
}
|
393 |
-
|
394 |
-
if ($this->audio_path
|
395 |
-
$this->audio_path = $this->securimage_path . '/audio/';
|
396 |
}
|
397 |
-
|
398 |
-
if ($this->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
399 |
$this->code_length = 6;
|
400 |
}
|
401 |
-
|
402 |
-
if ($this->perturbation
|
403 |
$this->perturbation = 0.75;
|
404 |
}
|
405 |
-
|
406 |
-
if ($this->namespace
|
407 |
$this->namespace = 'default';
|
408 |
}
|
409 |
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
492 |
|
493 |
-
|
494 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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[
|
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 |
-
|
664 |
{
|
665 |
$this->code = false;
|
666 |
|
667 |
switch($this->captcha_type) {
|
668 |
case self::SI_CAPTCHA_MATHEMATIC:
|
669 |
{
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
|
675 |
-
|
676 |
-
|
677 |
-
|
678 |
-
|
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] =
|
751 |
-
$py[$i] =
|
752 |
-
$rad[$i] =
|
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 =
|
802 |
-
|
803 |
$theta = ($this->frand() - 0.5) * M_PI * 0.7;
|
804 |
$w = $this->image_width;
|
805 |
-
$len =
|
806 |
-
$lwid =
|
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 =
|
850 |
-
$y =
|
851 |
-
$size =
|
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 |
-
|
888 |
-
|
889 |
-
|
890 |
-
|
891 |
-
|
892 |
-
|
893 |
-
|
894 |
-
|
895 |
-
header("
|
896 |
-
|
897 |
-
|
898 |
-
|
899 |
-
|
900 |
-
|
901 |
-
|
902 |
-
|
903 |
-
|
904 |
-
|
905 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
906 |
}
|
907 |
-
|
908 |
imagedestroy($this->im);
|
909 |
-
|
|
|
|
|
910 |
}
|
911 |
-
|
912 |
/**
|
913 |
* Gets the code and returns the binary audio file for the stored captcha code
|
914 |
-
*
|
|
|
915 |
*/
|
916 |
-
protected function getAudibleCode(
|
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->
|
927 |
-
|
|
|
|
|
|
|
|
|
928 |
}
|
929 |
|
930 |
-
|
931 |
-
$
|
932 |
-
|
933 |
-
|
934 |
-
|
935 |
-
|
|
|
|
|
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 =
|
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 |
-
|
953 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 ($
|
961 |
-
return
|
962 |
-
} else
|
963 |
-
|
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 |
-
|
977 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
992 |
-
|
993 |
-
|
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 |
-
$
|
1010 |
-
|
|
|
|
|
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 |
-
$
|
1044 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1045 |
|
1046 |
-
$this->
|
|
|
|
|
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->
|
1059 |
-
$
|
1060 |
-
$
|
1061 |
-
|
1062 |
-
|
1063 |
-
|
1064 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1065 |
}
|
1066 |
|
1067 |
return $success !== false;
|
1068 |
}
|
1069 |
-
|
1070 |
/**
|
1071 |
* Open sqlite database
|
1072 |
*/
|
1073 |
protected function openDatabase()
|
1074 |
{
|
1075 |
-
$this->
|
1076 |
|
1077 |
-
if ($this->
|
1078 |
-
$
|
1079 |
|
1080 |
-
if ($
|
1081 |
-
|
1082 |
-
|
1083 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1084 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1085 |
}
|
1086 |
|
1087 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1088 |
}
|
|
|
|
|
|
|
|
|
|
|
1089 |
|
1090 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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->
|
1101 |
-
|
1102 |
-
|
1103 |
-
|
1104 |
-
|
1105 |
-
|
1106 |
-
$
|
1107 |
-
|
1108 |
-
|
1109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 (
|
1122 |
$ip = $_SERVER['REMOTE_ADDR'];
|
1123 |
-
$ns =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1124 |
|
1125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1126 |
}
|
1127 |
}
|
1128 |
-
|
1129 |
/**
|
1130 |
* Deletes old codes from sqlite database
|
1131 |
*/
|
1132 |
protected function purgeOldCodesFromDatabase()
|
1133 |
{
|
1134 |
-
if ($this->
|
1135 |
$now = time();
|
1136 |
$limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time;
|
1137 |
|
1138 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
-
$
|
1179 |
-
$
|
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 |
-
|
1199 |
-
$
|
1200 |
-
|
1201 |
-
|
1202 |
-
|
1203 |
-
|
1204 |
-
|
1205 |
-
|
1206 |
-
|
1207 |
-
|
1208 |
-
|
1209 |
-
|
1210 |
-
|
1211 |
-
|
1212 |
-
|
1213 |
-
|
1214 |
-
|
1215 |
-
|
1216 |
-
|
1217 |
-
|
1218 |
-
|
1219 |
-
}
|
1220 |
-
|
1221 |
-
|
1222 |
-
|
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 |
-
|
1237 |
-
|
1238 |
-
|
1239 |
-
|
1240 |
-
|
1241 |
-
|
1242 |
-
|
1243 |
-
|
1244 |
-
|
1245 |
-
|
1246 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
1257 |
-
|
1258 |
-
|
1259 |
-
|
1260 |
-
|
1261 |
-
$out_data = substr_replace($out_data, pack('V', $numSamples), 40 + ($info['SubChunk1Size'] - 16), 4);
|
1262 |
|
1263 |
-
|
1264 |
-
|
1265 |
-
|
|
|
|
|
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 |
-
$
|
1276 |
-
|
1277 |
-
|
1278 |
-
|
1279 |
-
|
1280 |
-
|
1281 |
-
|
1282 |
-
|
1283 |
-
|
1284 |
-
|
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 |
-
|
1295 |
-
|
1296 |
-
$
|
|
|
|
|
|
|
1297 |
}
|
1298 |
|
1299 |
-
return $
|
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 *
|
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
|
31 |
* @author Drew Phillips <drew@drew-phillips.com>
|
32 |
-
* @version 3.
|
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)
|
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
|
41 |
* @author Drew Phillips <drew@drew-phillips.com>
|
42 |
-
* @version 3.
|
43 |
* @package Securimage
|
44 |
*
|
45 |
*/
|
@@ -49,15 +49,15 @@
|
|
49 |
|
50 |
require_once dirname(__FILE__) . '/securimage.php';
|
51 |
|
52 |
-
$img = new
|
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; //
|
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();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|