├── README.txt ├── faxnotify.php ├── faxout.pl ├── links ├── styles.css └── val.js ├── sendfax.php └── sendfaxform.html /README.txt: -------------------------------------------------------------------------------- 1 | ============================================================================ 2 | Web Fax for Asterisk php script 3 | ---------------------------------------------------------------------------- 4 | - written by recluze (http://csrdu.org/nauman) 5 | - queries, comments to: nauman@csrdu.org or recluze@gmail.com 6 | ============================================================================ 7 | 8 | Redistribution allowed provided this notice remains visible at the top. 9 | Released under GPLv3. 10 | 11 | 12 | Installation instructions: 13 | 0. Make sure you have Fax for Asterisk (or Free Fax for Asterisk) installed. 14 | See [http://downloads.digium.com/pub/telephony/fax/README] 15 | for details on that. Also make sure you have the FAX module installed 16 | in FreePBX and configured to send out faxes. You will also need a service 17 | provided (ITSP) that supports T.38 faxing. 18 | 19 | 1. Extract all Web Fax files to somewhere on your web server's htdocs. If you're 20 | running apache as user asterisk, you need to chown the Web Fax files. 21 | 22 | > chown asterisk:asterisk /webfax -R 23 | 24 | 2. If you're running asterisk as the user 'asterisk', chown the faxnotify.php script: 25 | 26 | > chown asterisk:asterisk faxnotify.php 27 | 28 | 3. Make faxnotify.php executable. It will be called from the dialplan from 29 | within asterisk: 30 | 31 | > chmod +x faxnotify.php 32 | 33 | 4. Move faxnotify.php to $ASTERISDIR/bin/faxnotify.php 34 | 35 | > mv /var/www/html/webfax/faxnotify.php /var/lib/asterisk/bin/faxnotify.php 36 | 37 | 5. Create a dialplan with [outboundfax] in extensions_additional.conf with the 38 | following content: 39 | 40 | [outboundfax] 41 | ; exten => s,1,NoOp(send a fax) 42 | exten => s,1,Set(FAXOPT(filename)=${FAXFILE}) 43 | exten => s,n,Set(FAXOPT(ecm)=yes) 44 | exten => s,n,Set(FAXOPT(headerinfo)=${FAXHEADER}) 45 | exten => s,n,Set(FAXOPT(localstationid)=${LOCALID}) 46 | exten => s,n,Set(FAXOPT(maxrate)=14400) 47 | exten => s,n,Set(FAXOPT(minrate)=2400) 48 | exten => s,n,SendFAX(${FAXFILE},d) 49 | exten => s,n,System(${ASTVARLIBDIR}/bin/faxnotify.php INIT "${EMAIL}" "${DESTINATION}" "${TIMESTAMP}" "NO_STATUS" "NO_PAGES") 50 | exten => h,1,NoOp(FAXOPT(ecm) : ${FAXOPT(ecm)}) 51 | exten => h,n,NoOp(FaxStatus : ${FAXSTATUS}) 52 | exten => h,n,NoOp(FaxStatusString : ${FAXSTATUSSTRING}) 53 | exten => h,n,NoOp(FaxError : ${FAXERROR}) 54 | exten => h,n,NoOp(RemoteStationID : ${REMOTESTATIONID}) 55 | exten => h,n,NoOp(FaxPages : ${FAXPAGES}) 56 | exten => h,n,NoOp(FaxBitRate : ${FAXBITRATE}) 57 | exten => h,n,NoOp(FaxResolution : ${FAXRESOLUTION}) 58 | exten => h,n,System(${ASTVARLIBDIR}/bin/faxnotify.php NOTIFY "${EMAIL}" "${DESTINATION}" "${TIMESTAMP}" "${FAXSTATUSSTRING}" "${FAXPAGES}") 59 | ; end of outboundfax context 60 | 61 | (You can also use the following command to add the dialplan directly to extensions_additional.conf 62 | [not yet implemented] 63 | 64 | > chmod +x insert_dialplan.sh 65 | > ./insert_dialplan.sh 66 | 67 | Usage: 68 | 69 | 1. Point your browser to sendfaxform.html and see the magic happen. -------------------------------------------------------------------------------- /faxnotify.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | "; 26 | 27 | // end setting up 28 | 29 | if ($messtype == "INIT") { // SendFAX called successfully in the dialplan... 30 | $emailSubject = "Your fax to $dest has been initiated"; 31 | $notice = "Your fax to $dest sent on $timestamp has been initiated. You will get a notification " . 32 | "email when the transmission is complete. "; 33 | $emailBody = "Hi! \n\n" . $notice . " \n\n " . 34 | "If you have any queries, please contact us at: $CONTACT_EMAIL"; 35 | mail($email, $emailSubject, $emailBody, $headers); 36 | } 37 | else { // meaning $messtype = "NOTIFY" ... sending of fax is complete. Need to check if SUCCEEDED 38 | $tech_details = "------------------------------ \n". 39 | "DESTINATION = $dest \n". 40 | "TIMESTAMP = $timestamp \n". 41 | "FAXOPTS_STATUSSTRING = $status \n". 42 | "NUM_PAGES = $numpages \n". 43 | "------------------------------ \n"; 44 | 45 | 46 | echo "Sending fax notification email to: $email from $FROM_EMAIL \n"; 47 | 48 | if($status == $SUCCESS_STATUS) { 49 | $emailSubject = "Your fax to $dest was delivered successfully"; 50 | $notice = "This is an automated response to let you know that your fax to " . 51 | "$dest sent on $timestamp was delivered successfully. \n"; 52 | } else { 53 | $emailSubject = "Your fax to $dest could not be sent"; 54 | $notice = "This is an automated response to let you know that your fax to " . 55 | "$dest sent on $timestamp could not be delivered. \n"; 56 | } 57 | 58 | $emailBody = "Hi! \n\n" . $notice . "\n\n" . $tech_details . " \n\n " . 59 | "If you have any queries, please contact us at: $CONTACT_EMAIL"; 60 | 61 | // echo $emailSubject . "\n"; 62 | // echo $emailBody . "\n"; 63 | 64 | // mail 65 | mail($email, $emailSubject, $emailBody, $headers ); 66 | // exec("echo $email $timestamp $emailSubject >> /var/log/asterisk/webfax.log"); 67 | // exec("echo $emailBody >> /var/log/asterisk/webfax.log"); 68 | // exec("echo -------------------------------- >> /var/log/asterisk/webfax.log"); 69 | } 70 | ?> 71 | -------------------------------------------------------------------------------- /faxout.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use strict; 3 | use warnings; 4 | sub random_name_generator($); 5 | 6 | # usage: faxout.pl faxHeader localID email dest filename 7 | # example: faxout.pl "My Fax Header" 1213435151 3124348989 myfax.pdf 8 | 9 | if ($#ARGV != 4) { 10 | print qq(FAIL: 5 Arguments needed\n); 11 | exit(1); 12 | } 13 | 14 | my ($faxHeader,$localID,$email,$dest,$filename,$callfile,$callfilename); 15 | 16 | $faxHeader = $ARGV[0]; 17 | $localID = $ARGV[1]; 18 | $email = $ARGV[2]; 19 | $dest = $ARGV[3]; 20 | $filename = $ARGV[4]; 21 | 22 | if ($dest) { 23 | $callfilename = &random_name_generator(12).".call"; 24 | open (MYFILE, ">>/tmp/$callfilename") or die $!; 25 | # $callfile = "Channel: Local/$callto\@outboundialcontext\n"; 26 | $callfile = "Channel: Local/$callto\@outbound-allroutes\n"; 27 | # $callfile = "Channel: SIP/$callto\n"; 28 | $callfile = $callfile . "MaxRetries: 1\n"; 29 | $callfile = $callfile . "RetryTime: 60\n"; 30 | $callfile = $callfile . "WaitTime: 60\n"; 31 | $callfile = $callfile . "Archive: yes\n"; 32 | $callfile = $callfile . "Context: outboundfax\n"; 33 | $callfile = $callfile . "Extension: s\n"; 34 | $callfile = $callfile . "Priority: 1\n"; 35 | # print qq(Sending file: ); 36 | # print qq($tifname\n); 37 | # ---------------- settting fax information ---------------- 38 | $callfile = $callfile . "Set: FAXFILE=$filename\n"; 39 | $callfile = $callfile . "Set: FAXHEADER=$faxHeader\n"; 40 | $callfile = $callfile . "Set: LOCALID\n"; 41 | 42 | # ---- sending call file now ------ 43 | print MYFILE $callfile; 44 | close (MYFILE); 45 | system("mv /tmp/$callfilename /var/spool/asterisk/outgoing"); 46 | } 47 | 48 | sub random_name_generator($) { 49 | my ($namelength, $randomstring, @chars); 50 | $namelength = shift; 51 | @chars = ('a'..'z','A'..'Z','0'..'9'); 52 | foreach (1..$namelength) { 53 | $randomstring .= $chars[rand @chars]; 54 | } 55 | return $randomstring; 56 | } 57 | # Taken from: http://www.teamforrest.com/blog/156/integrating-fax-for-asterisk/ 58 | -------------------------------------------------------------------------------- /links/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 100%/1.4 Verdana, Arial, Helvetica, sans-serif; 3 | background: #42413C; 4 | margin: 0; 5 | padding: 0; 6 | color: #000; 7 | } 8 | 9 | /* ~~ Element/tag selectors ~~ */ 10 | ul, ol, dl { /* Due to variations between browsers, it's best practices to zero padding and margin on lists. For consistency, you can either specify the amounts you want here, or on the list items (LI, DT, DD) they contain. Remember that what you do here will cascade to the .nav list unless you write a more specific selector. */ 11 | padding: 0; 12 | margin: 0; 13 | } 14 | h1, h2, h3, h4, h5, h6, p { 15 | margin-top: 0; /* removing the top margin gets around an issue where margins can escape from their containing div. The remaining bottom margin will hold it away from any elements that follow. */ 16 | padding-right: 15px; 17 | padding-left: 15px; /* adding the padding to the sides of the elements within the divs, instead of the divs themselves, gets rid of any box model math. A nested div with side padding can also be used as an alternate method. */ 18 | } 19 | a img { /* this selector removes the default blue border displayed in some browsers around an image when it is surrounded by a link */ 20 | border: none; 21 | } 22 | 23 | .content p { 24 | margin-bottom:30px; 25 | } 26 | td { 27 | padding: 5px; 28 | padding-left: 15px; 29 | vertical-align: top; 30 | border-top: 1px solid #ededed; 31 | } 32 | /* ~~ Styling for your site's links must remain in this order - including the group of selectors that create the hover effect. ~~ */ 33 | a:link { 34 | color: #42413C; 35 | text-decoration: underline; /* unless you style your links to look extremely unique, it's best to provide underlines for quick visual identification */ 36 | } 37 | a:visited { 38 | color: #6E6C64; 39 | text-decoration: underline; 40 | } 41 | a:hover, a:active, a:focus { /* this group of selectors will give a keyboard navigator the same hover experience as the person using a mouse. */ 42 | text-decoration: none; 43 | } 44 | 45 | /* ~~ this fixed width container surrounds all other elements ~~ */ 46 | .container { 47 | width: 960px; 48 | background: #FFF; 49 | margin: 0 auto; /* the auto value on the sides, coupled with the width, centers the layout */ 50 | } 51 | 52 | /* ~~ This is the layout information. ~~ 53 | 54 | 1) Padding is only placed on the top and/or bottom of the div. The elements within this div have padding on their sides. This saves you from any "box model math". Keep in mind, if you add any side padding or border to the div itself, it will be added to the width you define to create the *total* width. You may also choose to remove the padding on the element in the div and place a second div within it with no width and the padding necessary for your design. 55 | 56 | */ 57 | .content { 58 | padding: 20px ; 59 | } 60 | .container { 61 | /* padding: 20px; */ 62 | } 63 | /* ~~ miscellaneous float/clear classes ~~ */ 64 | .fltrt { /* this class can be used to float an element right in your page. The floated element must precede the element it should be next to on the page. */ 65 | float: right; 66 | margin-left: 8px; 67 | } 68 | .fltlft { /* this class can be used to float an element left in your page. The floated element must precede the element it should be next to on the page. */ 69 | float: left; 70 | margin-right: 8px; 71 | } 72 | .clearfloat { /* this class can be placed on a
or empty div as the final element following the last floated div (within the #container) if the overflow:hidden on the .container is removed */ 73 | clear:both; 74 | height:0; 75 | font-size: 1px; 76 | line-height: 0px; 77 | } 78 | /* SpryValidationTextField.css - version 0.4 - Spry Pre-Release 1.6.1 */ 79 | 80 | /* Copyright (c) 2006. Adobe Systems Incorporated. All rights reserved. */ 81 | 82 | 83 | /* These are the classes applied on the error messages 84 | * which prevent them from being displayed by default. 85 | */ 86 | .textfieldRequiredMsg, 87 | .textfieldInvalidFormatMsg, 88 | .textfieldMinValueMsg, 89 | .textfieldMaxValueMsg, 90 | .textfieldMinCharsMsg, 91 | .textfieldMaxCharsMsg, 92 | .textfieldValidMsg { 93 | display: none; 94 | } 95 | 96 | /* These selectors change the way messages look when the widget is in one of the error states. 97 | * These classes set a default red border and color for the error text. 98 | * The state class (e.g. .textfieldRequiredState) is applied on the top-level container for the widget, 99 | * and this way only the specific error message can be shown by setting the display property to "inline". 100 | */ 101 | .textfieldRequiredState .textfieldRequiredMsg, 102 | .textfieldInvalidFormatState .textfieldInvalidFormatMsg, 103 | .textfieldMinValueState .textfieldMinValueMsg, 104 | .textfieldMaxValueState .textfieldMaxValueMsg, 105 | .textfieldMinCharsState .textfieldMinCharsMsg, 106 | .textfieldMaxCharsState .textfieldMaxCharsMsg 107 | { 108 | display: inline; 109 | font-size: 10px; 110 | color: #CC3333; 111 | /* border: 1px solid #CC3333; */ 112 | } 113 | 114 | 115 | 116 | /* The next three group selectors control the way the core element (INPUT) looks like when the widget is in one of the states: * focus, required / invalid / minValue / maxValue / minChars / maxChars , valid 117 | * There are two selectors for each state, to cover the two main usecases for the widget: 118 | * - the widget id is placed on the top level container for the INPUT 119 | * - the widget id is placed on the INPUT element itself (there are no error messages) 120 | */ 121 | 122 | /* When the widget is in the valid state the INPUT has a green background applied on it. */ 123 | .textfieldValidState input, input.textfieldValidState { 124 | background-color: #B8F5B1; 125 | } 126 | 127 | /* When the widget is in an invalid state the INPUT has a red background applied on it. */ 128 | input.textfieldRequiredState, .textfieldRequiredState input, 129 | input.textfieldInvalidFormatState, .textfieldInvalidFormatState input, 130 | input.textfieldMinValueState, .textfieldMinValueState input, 131 | input.textfieldMaxValueState, .textfieldMaxValueState input, 132 | input.textfieldMinCharsState, .textfieldMinCharsState input, 133 | input.textfieldMaxCharsState, .textfieldMaxCharsState input { 134 | background-color: #FF9F9F; 135 | } 136 | 137 | /* When the widget has received focus, the INPUT has a yellow background applied on it. */ 138 | .textfieldFocusState input, input.textfieldFocusState { 139 | background-color: #FFFFCC; 140 | } 141 | 142 | /* This class applies only for a short period of time and changes the way the text in the textbox looks like. 143 | * It applies only when the widget has character masking enabled and the user tries to type in an invalid character. 144 | */ 145 | .textfieldFlashText input, input.textfieldFlashText { 146 | color: red !important; 147 | } 148 | 149 | /* When the widget has the hint message on, the hint text can be styled differently than the user typed text. */ 150 | .textfieldHintState input, input.textfieldHintState { 151 | /*color: red !important;*/ 152 | } 153 | 154 | 155 | .descriptionText{ 156 | font-size: 10px; 157 | color: #666; 158 | background-color: #eee; 159 | } 160 | 161 | .formLabel{ 162 | font-size: 12px; 163 | color: #666; 164 | background-color: #fff; 165 | } 166 | 167 | 168 | .footer-copyrights{ 169 | font-size: 10px; 170 | color: #999; 171 | padding: 0px; 172 | margin: 0px; 173 | width: 960px; 174 | margin: 0 auto; /* the auto value on the sides, coupled with the width, centers the layout */ 175 | } 176 | .footer-copyrights a{ 177 | color: #999; 178 | text-decoration: none; 179 | border-bottom: 1px dotted #999; 180 | } 181 | 182 | .error { 183 | color: #900; 184 | } -------------------------------------------------------------------------------- /links/val.js: -------------------------------------------------------------------------------- 1 | // SpryValidationTextField.js - version 0.38 - Spry Pre-Release 1.6.1 2 | // 3 | // Copyright (c) 2006. Adobe Systems Incorporated. 4 | // All rights reserved. 5 | // 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions are met: 8 | // 9 | // * Redistributions of source code must retain the above copyright notice, 10 | // this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above copyright notice, 12 | // this list of conditions and the following disclaimer in the documentation 13 | // and/or other materials provided with the distribution. 14 | // * Neither the name of Adobe Systems Incorporated nor the names of its 15 | // contributors may be used to endorse or promote products derived from this 16 | // software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | // POSSIBILITY OF SUCH DAMAGE. 29 | 30 | (function() { // BeginSpryComponent 31 | 32 | if (typeof Spry == "undefined") window.Spry = {}; if (!Spry.Widget) Spry.Widget = {}; 33 | 34 | Spry.Widget.BrowserSniff = function() 35 | { 36 | var b = navigator.appName.toString(); 37 | var up = navigator.platform.toString(); 38 | var ua = navigator.userAgent.toString(); 39 | 40 | this.mozilla = this.ie = this.opera = this.safari = false; 41 | var re_opera = /Opera.([0-9\.]*)/i; 42 | var re_msie = /MSIE.([0-9\.]*)/i; 43 | var re_gecko = /gecko/i; 44 | var re_safari = /(applewebkit|safari)\/([\d\.]*)/i; 45 | var r = false; 46 | 47 | if ( (r = ua.match(re_opera))) { 48 | this.opera = true; 49 | this.version = parseFloat(r[1]); 50 | } else if ( (r = ua.match(re_msie))) { 51 | this.ie = true; 52 | this.version = parseFloat(r[1]); 53 | } else if ( (r = ua.match(re_safari))) { 54 | this.safari = true; 55 | this.version = parseFloat(r[2]); 56 | } else if (ua.match(re_gecko)) { 57 | var re_gecko_version = /rv:\s*([0-9\.]+)/i; 58 | r = ua.match(re_gecko_version); 59 | this.mozilla = true; 60 | this.version = parseFloat(r[1]); 61 | } 62 | this.windows = this.mac = this.linux = false; 63 | 64 | this.Platform = ua.match(/windows/i) ? "windows" : 65 | (ua.match(/linux/i) ? "linux" : 66 | (ua.match(/mac/i) ? "mac" : 67 | ua.match(/unix/i)? "unix" : "unknown")); 68 | this[this.Platform] = true; 69 | this.v = this.version; 70 | 71 | if (this.safari && this.mac && this.mozilla) { 72 | this.mozilla = false; 73 | } 74 | }; 75 | 76 | Spry.is = new Spry.Widget.BrowserSniff(); 77 | 78 | Spry.Widget.ValidationTextField = function(element, type, options) 79 | { 80 | type = Spry.Widget.Utils.firstValid(type, "none"); 81 | if (typeof type != 'string') { 82 | this.showError('The second parameter in the constructor should be the validation type, the options are the third parameter.'); 83 | return; 84 | } 85 | if (typeof Spry.Widget.ValidationTextField.ValidationDescriptors[type] == 'undefined') { 86 | this.showError('Unknown validation type received as the second parameter.'); 87 | return; 88 | } 89 | options = Spry.Widget.Utils.firstValid(options, {}); 90 | this.type = type; 91 | if (!this.isBrowserSupported()) { 92 | //disable character masking and pattern behaviors for low level browsers 93 | options.useCharacterMasking = false; 94 | } 95 | this.init(element, options); 96 | 97 | //make sure we validate at least on submit 98 | var validateOn = ['submit'].concat(Spry.Widget.Utils.firstValid(this.options.validateOn, [])); 99 | validateOn = validateOn.join(","); 100 | 101 | this.validateOn = 0; 102 | this.validateOn = this.validateOn | (validateOn.indexOf('submit') != -1 ? Spry.Widget.ValidationTextField.ONSUBMIT : 0); 103 | this.validateOn = this.validateOn | (validateOn.indexOf('blur') != -1 ? Spry.Widget.ValidationTextField.ONBLUR : 0); 104 | this.validateOn = this.validateOn | (validateOn.indexOf('change') != -1 ? Spry.Widget.ValidationTextField.ONCHANGE : 0); 105 | 106 | if (Spry.Widget.ValidationTextField.onloadDidFire) 107 | this.attachBehaviors(); 108 | else 109 | Spry.Widget.ValidationTextField.loadQueue.push(this); 110 | }; 111 | 112 | Spry.Widget.ValidationTextField.ONCHANGE = 1; 113 | Spry.Widget.ValidationTextField.ONBLUR = 2; 114 | Spry.Widget.ValidationTextField.ONSUBMIT = 4; 115 | 116 | Spry.Widget.ValidationTextField.ERROR_REQUIRED = 1; 117 | Spry.Widget.ValidationTextField.ERROR_FORMAT = 2; 118 | Spry.Widget.ValidationTextField.ERROR_RANGE_MIN = 4; 119 | Spry.Widget.ValidationTextField.ERROR_RANGE_MAX = 8; 120 | Spry.Widget.ValidationTextField.ERROR_CHARS_MIN = 16; 121 | Spry.Widget.ValidationTextField.ERROR_CHARS_MAX = 32; 122 | 123 | /* validation parameters: 124 | * - characterMasking : prevent typing of characters not matching an regular expression 125 | * - regExpFilter : additional regular expression to disalow typing of characters 126 | * (like the "-" sign in the middle of the value); use for partial matching of the currently typed value; 127 | * the typed value must match regExpFilter at any moment 128 | * - pattern : enforce character on each position inside a pattern (AX0?) 129 | * - validation : function performing logic validation; return false if failed and the typedValue value on success 130 | * - minValue, maxValue : range validation; check if typedValue inside the specified range 131 | * - minChars, maxChars : value length validation; at least/at most number of characters 132 | * */ 133 | Spry.Widget.ValidationTextField.ValidationDescriptors = { 134 | 'none': { 135 | }, 136 | 'custom': { 137 | }, 138 | 'integer': { 139 | characterMasking: /[\-\+\d]/, 140 | regExpFilter: /^[\-\+]?\d*$/, 141 | validation: function(value, options) { 142 | if (value == '' || value == '-' || value == '+') { 143 | return false; 144 | } 145 | var regExp = /^[\-\+]?\d*$/; 146 | if (!regExp.test(value)) { 147 | return false; 148 | } 149 | options = options || {allowNegative:false}; 150 | var ret = parseInt(value, 10); 151 | if (!isNaN(ret)) { 152 | var allowNegative = true; 153 | if (typeof options.allowNegative != 'undefined' && options.allowNegative == false) { 154 | allowNegative = false; 155 | } 156 | if (!allowNegative && value < 0) { 157 | ret = false; 158 | } 159 | } else { 160 | ret = false; 161 | } 162 | return ret; 163 | } 164 | }, 165 | 'real': { 166 | characterMasking: /[\d\.,\-\+e]/i, 167 | regExpFilter: /^[\-\+]?\d(?:|\.,\d{0,2})|(?:|e{0,1}[\-\+]?\d{0,})$/i, 168 | validation: function (value, options) { 169 | var regExp = /^[\+\-]?[0-9]+([\.,][0-9]+)?([eE]{0,1}[\-\+]?[0-9]+)?$/; 170 | if (!regExp.test(value)) { 171 | return false; 172 | } 173 | var ret = parseFloat(value); 174 | if (isNaN(ret)) { 175 | ret = false; 176 | } 177 | return ret; 178 | } 179 | }, 180 | 'currency': { 181 | formats: { 182 | 'dot_comma': { 183 | characterMasking: /[\d\.\,\-\+\$]/, 184 | regExpFilter: /^[\-\+]?(?:[\d\.]*)+(|\,\d{0,2})$/, 185 | validation: function(value, options) { 186 | var ret = false; 187 | //2 or no digits after the comma 188 | if (/^(\-|\+)?\d{1,3}(?:\.\d{3})*(?:\,\d{2}|)$/.test(value) || /^(\-|\+)?\d+(?:\,\d{2}|)$/.test(value)) { 189 | value = value.toString().replace(/\./gi, '').replace(/\,/, '.'); 190 | ret = parseFloat(value); 191 | } 192 | return ret; 193 | } 194 | }, 195 | 'comma_dot': { 196 | characterMasking: /[\d\.\,\-\+\$]/, 197 | regExpFilter: /^[\-\+]?(?:[\d\,]*)+(|\.\d{0,2})$/, 198 | validation: function(value, options) { 199 | var ret = false; 200 | //2 or no digits after the comma 201 | if (/^(\-|\+)?\d{1,3}(?:\,\d{3})*(?:\.\d{2}|)$/.test(value) || /^(\-|\+)?\d+(?:\.\d{2}|)$/.test(value)) { 202 | value = value.toString().replace(/\,/gi, ''); 203 | ret = parseFloat(value); 204 | } 205 | return ret; 206 | } 207 | } 208 | } 209 | }, 210 | 'email': { 211 | characterMasking: /[^\s]/, 212 | validation: function(value, options) { 213 | var rx = /^[\w\.-]+@[\w\.-]+\.\w+$/i; 214 | return rx.test(value); 215 | } 216 | }, 217 | 'date': { 218 | validation: function(value, options) { 219 | var formatRegExp = /^([mdy]+)[\.\-\/\\\s]+([mdy]+)[\.\-\/\\\s]+([mdy]+)$/i; 220 | var valueRegExp = this.dateValidationPattern; 221 | var formatGroups = options.format.match(formatRegExp); 222 | var valueGroups = value.match(valueRegExp); 223 | if (formatGroups !== null && valueGroups !== null) { 224 | var dayIndex = -1; 225 | var monthIndex = -1; 226 | var yearIndex = -1; 227 | for (var i=1; i 12) { 249 | return false; 250 | } 251 | 252 | // Calculate the maxDay according to the current month 253 | switch (theMonth) { 254 | case 1: // January 255 | case 3: // March 256 | case 5: // May 257 | case 7: // July 258 | case 8: // August 259 | case 10: // October 260 | case 12: // December 261 | maxDay = 31; 262 | break; 263 | case 4: // April 264 | case 6: // June 265 | case 9: // September 266 | case 11: // November 267 | maxDay = 30; 268 | break; 269 | case 2: // February 270 | if ((parseInt(theYear/4, 10) * 4 == theYear) && (theYear % 100 != 0 || theYear % 400 == 0)) { 271 | maxDay = 29; 272 | } else { 273 | maxDay = 28; 274 | } 275 | break; 276 | } 277 | 278 | // Check day value to be between 1..maxDay 279 | if (theDay < 1 || theDay > maxDay) { 280 | return false; 281 | } 282 | 283 | // If successfull we'll return the date object 284 | return (new Date(theYear, theMonth - 1, theDay)); //JavaScript requires a month between 0 and 11 285 | } 286 | } else { 287 | return false; 288 | } 289 | } 290 | }, 291 | 'time': { 292 | validation: function(value, options) { 293 | // HH:MM:SS T 294 | var formatRegExp = /([hmst]+)/gi; 295 | var valueRegExp = /(\d+|AM?|PM?)/gi; 296 | var formatGroups = options.format.match(formatRegExp); 297 | var valueGroups = value.match(valueRegExp); 298 | //mast match and have same length 299 | if (formatGroups !== null && valueGroups !== null) { 300 | if (formatGroups.length != valueGroups.length) { 301 | return false; 302 | } 303 | 304 | var hourIndex = -1; 305 | var minuteIndex = -1; 306 | var secondIndex = -1; 307 | //T is AM or PM 308 | var tIndex = -1; 309 | var theHour = 0, theMinute = 0, theSecond = 0, theT = 'AM'; 310 | for (var i=0; i (formatGroups[hourIndex] == 'HH' ? 23 : 12 )) { 330 | return false; 331 | } 332 | } 333 | if (minuteIndex != -1) { 334 | var theMinute = parseInt(valueGroups[minuteIndex], 10); 335 | if (isNaN(theMinute) || theMinute > 59) { 336 | return false; 337 | } 338 | } 339 | if (secondIndex != -1) { 340 | var theSecond = parseInt(valueGroups[secondIndex], 10); 341 | if (isNaN(theSecond) || theSecond > 59) { 342 | return false; 343 | } 344 | } 345 | if (tIndex != -1) { 346 | var theT = valueGroups[tIndex].toUpperCase(); 347 | if ( 348 | formatGroups[tIndex].toUpperCase() == 'TT' && !/^a|pm$/i.test(theT) || 349 | formatGroups[tIndex].toUpperCase() == 'T' && !/^a|p$/i.test(theT) 350 | ) { 351 | return false; 352 | } 353 | } 354 | var date = new Date(2000, 0, 1, theHour + (theT.charAt(0) == 'P'?12:0), theMinute, theSecond); 355 | return date; 356 | } else { 357 | return false; 358 | } 359 | } 360 | }, 361 | 'credit_card': { 362 | characterMasking: /\d/, 363 | validation: function(value, options) { 364 | var regExp = null; 365 | options.format = options.format || 'ALL'; 366 | switch (options.format.toUpperCase()) { 367 | case 'ALL': regExp = /^[3-6]{1}[0-9]{12,18}$/; break; 368 | case 'VISA': regExp = /^4(?:[0-9]{12}|[0-9]{15})$/; break; 369 | case 'MASTERCARD': regExp = /^5[1-5]{1}[0-9]{14}$/; break; 370 | case 'AMEX': regExp = /^3(4|7){1}[0-9]{13}$/; break; 371 | case 'DISCOVER': regExp = /^6011[0-9]{12}$/; break; 372 | case 'DINERSCLUB': regExp = /^3(?:(0[0-5]{1}[0-9]{11})|(6[0-9]{12})|(8[0-9]{12}))$/; break; 373 | } 374 | if (!regExp.test(value)) { 375 | return false; 376 | } 377 | var digits = []; 378 | var j = 1, digit = ''; 379 | for (var i = value.length - 1; i >= 0; i--) { 380 | if ((j%2) == 0) { 381 | digit = parseInt(value.charAt(i), 10) * 2; 382 | digits[digits.length] = digit.toString().charAt(0); 383 | if (digit.toString().length == 2) { 384 | digits[digits.length] = digit.toString().charAt(1); 385 | } 386 | } else { 387 | digit = value.charAt(i); 388 | digits[digits.length] = digit; 389 | } 390 | j++; 391 | } 392 | var sum = 0; 393 | for(i=0; i < digits.length; i++ ) { 394 | sum += parseInt(digits[i], 10); 395 | } 396 | if ((sum%10) == 0) { 397 | return true; 398 | } 399 | return false; 400 | } 401 | }, 402 | 'zip_code': { 403 | formats: { 404 | 'zip_us9': { 405 | pattern:'00000-0000' 406 | }, 407 | 'zip_us5': { 408 | pattern:'00000' 409 | }, 410 | 'zip_uk': { 411 | characterMasking: /[\dA-Z\s]/, 412 | validation: function(value, options) { 413 | //check one of the following masks 414 | // AN NAA, ANA NAA, ANN NAA, AAN NAA, AANA NAA, AANN NAA 415 | return /^[A-Z]{1,2}\d[\dA-Z]?\s?\d[A-Z]{2}$/.test(value); 416 | } 417 | }, 418 | 'zip_canada': { 419 | characterMasking: /[\dA-Z\s]/, 420 | pattern: 'A0A 0A0' 421 | }, 422 | 'zip_custom': {} 423 | } 424 | }, 425 | 'phone_number': { 426 | formats: { 427 | //US phone number; 10 digits 428 | 'phone_us': { 429 | pattern:'(000) 000-0000' 430 | }, 431 | 'phone_custom': {} 432 | } 433 | }, 434 | 'social_security_number': { 435 | pattern:'000-00-0000' 436 | }, 437 | 'ip': { 438 | characterMaskingFormats: { 439 | 'ipv4': /[\d\.]/i, 440 | 'ipv6_ipv4': /[\d\.\:A-F\/]/i, 441 | 'ipv6': /[\d\.\:A-F\/]/i 442 | }, 443 | validation: function (value, options) { 444 | return Spry.Widget.ValidationTextField.validateIP(value, options.format); 445 | } 446 | }, 447 | 448 | 'url': { 449 | characterMasking: /[^\s]/, 450 | validation: function(value, options) { 451 | //fix for ?ID=223429 and ?ID=223387 452 | /* the following regexp matches components of an URI as specified in http://tools.ietf.org/html/rfc3986#page-51 page 51, Appendix B. 453 | scheme = $2 454 | authority = $4 455 | path = $5 456 | query = $7 457 | fragment = $9 458 | */ 459 | var URI_spliter = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; 460 | var parts = value.match(URI_spliter); 461 | if (parts && parts[4]) { 462 | //encode each component of the domain name using Punycode encoding scheme: http://tools.ietf.org/html/rfc3492 463 | var host = parts[4].split("."); 464 | var punyencoded = ''; 465 | for (var i=0; i 1080::8:800:200C:417A 538 | FF01:0:0:0:0:0:0:101 --> FF01::101 539 | 0:0:0:0:0:0:0:1 --> ::1 540 | 0:0:0:0:0:0:0:0 --> :: 541 | 542 | 2.5.4 IPv6 Addresses with Embedded IPv4 Addresses 543 | IPv4-compatible IPv6 address (tunnel IPv6 packets over IPv4 routing infrastructures) 544 | ::0:129.144.52.38 545 | IPv4-mapped IPv6 address (represent the addresses of IPv4-only nodes as IPv6 addresses) 546 | ::ffff:129.144.52.38 547 | 548 | The text representation of IPv6 addresses and prefixes in Augmented BNF (Backus-Naur Form) [ABNF] for reference purposes. 549 | [ABNF http://tools.ietf.org/html/rfc2234] 550 | IPv6address = hexpart [ ":" IPv4address ] 551 | IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT 552 | 553 | IPv6prefix = hexpart "/" 1*2DIGIT 554 | 555 | hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ] 556 | hexseq = hex4 *( ":" hex4) 557 | hex4 = 1*4HEXDIG 558 | */ 559 | Spry.Widget.ValidationTextField.validateIP = function (value, format) 560 | { 561 | var validIPv6Addresses = [ 562 | //preferred 563 | /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}(?:\/\d{1,3})?$/i, 564 | 565 | //various compressed 566 | /^[a-f0-9]{0,4}::(?:\/\d{1,3})?$/i, 567 | /^:(?::[a-f0-9]{1,4}){1,6}(?:\/\d{1,3})?$/i, 568 | /^(?:[a-f0-9]{1,4}:){1,6}:(?:\/\d{1,3})?$/i, 569 | /^(?:[a-f0-9]{1,4}:)(?::[a-f0-9]{1,4}){1,6}(?:\/\d{1,3})?$/i, 570 | /^(?:[a-f0-9]{1,4}:){2}(?::[a-f0-9]{1,4}){1,5}(?:\/\d{1,3})?$/i, 571 | /^(?:[a-f0-9]{1,4}:){3}(?::[a-f0-9]{1,4}){1,4}(?:\/\d{1,3})?$/i, 572 | /^(?:[a-f0-9]{1,4}:){4}(?::[a-f0-9]{1,4}){1,3}(?:\/\d{1,3})?$/i, 573 | /^(?:[a-f0-9]{1,4}:){5}(?::[a-f0-9]{1,4}){1,2}(?:\/\d{1,3})?$/i, 574 | /^(?:[a-f0-9]{1,4}:){6}(?::[a-f0-9]{1,4})(?:\/\d{1,3})?$/i, 575 | 576 | 577 | //IPv6 mixes with IPv4 578 | /^(?:[a-f0-9]{1,4}:){6}(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i, 579 | /^:(?::[a-f0-9]{1,4}){0,4}:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i, 580 | /^(?:[a-f0-9]{1,4}:){1,5}:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i, 581 | /^(?:[a-f0-9]{1,4}:)(?::[a-f0-9]{1,4}){1,4}:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i, 582 | /^(?:[a-f0-9]{1,4}:){2}(?::[a-f0-9]{1,4}){1,3}:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i, 583 | /^(?:[a-f0-9]{1,4}:){3}(?::[a-f0-9]{1,4}){1,2}:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i, 584 | /^(?:[a-f0-9]{1,4}:){4}(?::[a-f0-9]{1,4}):(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,3})?$/i 585 | ]; 586 | var validIPv4Addresses = [ 587 | //IPv4 588 | /^(\d{1,3}\.){3}\d{1,3}$/i 589 | ]; 590 | var validAddresses = []; 591 | if (format == 'ipv6' || format == 'ipv6_ipv4') { 592 | validAddresses = validAddresses.concat(validIPv6Addresses); 593 | } 594 | if (format == 'ipv4' || format == 'ipv6_ipv4') { 595 | validAddresses = validAddresses.concat(validIPv4Addresses); 596 | } 597 | 598 | var ret = false; 599 | for (var i=0; i 255 || !regExp.test(pieces[i]) || pieces[i].length>3 || /^0{2,3}$/.test(pieces[i])) { 624 | return false; 625 | } 626 | } 627 | } 628 | if (ret && value.indexOf("/") != -1) { 629 | // if prefix-length is specified must be in [1-128] 630 | var prefLen = value.match(/\/\d{1,3}$/); 631 | if (!prefLen) return false; 632 | var prefLenVal = parseInt(prefLen[0].replace(/^\//,''), 10); 633 | if (isNaN(prefLenVal) || prefLenVal > 128 || prefLenVal < 1) { 634 | return false; 635 | } 636 | } 637 | return ret; 638 | }; 639 | 640 | Spry.Widget.ValidationTextField.onloadDidFire = false; 641 | Spry.Widget.ValidationTextField.loadQueue = []; 642 | 643 | Spry.Widget.ValidationTextField.prototype.isBrowserSupported = function() 644 | { 645 | return Spry.is.ie && Spry.is.v >= 5 && Spry.is.windows 646 | || 647 | Spry.is.mozilla && Spry.is.v >= 1.4 648 | || 649 | Spry.is.safari 650 | || 651 | Spry.is.opera && Spry.is.v >= 9; 652 | }; 653 | 654 | Spry.Widget.ValidationTextField.prototype.init = function(element, options) 655 | { 656 | this.element = this.getElement(element); 657 | this.errors = 0; 658 | this.flags = {locked: false, restoreSelection: true}; 659 | this.options = {}; 660 | this.event_handlers = []; 661 | 662 | this.validClass = "textfieldValidState"; 663 | this.focusClass = "textfieldFocusState"; 664 | this.requiredClass = "textfieldRequiredState"; 665 | this.hintClass = "textfieldHintState"; 666 | this.invalidFormatClass = "textfieldInvalidFormatState"; 667 | this.invalidRangeMinClass = "textfieldMinValueState"; 668 | this.invalidRangeMaxClass = "textfieldMaxValueState"; 669 | this.invalidCharsMinClass = "textfieldMinCharsState"; 670 | this.invalidCharsMaxClass = "textfieldMaxCharsState"; 671 | this.textfieldFlashTextClass = "textfieldFlashText"; 672 | if (Spry.is.safari) { 673 | this.flags.lastKeyPressedTimeStamp = 0; 674 | } 675 | 676 | switch (this.type) { 677 | case 'phone_number':options.format = Spry.Widget.Utils.firstValid(options.format, 'phone_us');break; 678 | case 'currency':options.format = Spry.Widget.Utils.firstValid(options.format, 'comma_dot');break; 679 | case 'zip_code':options.format = Spry.Widget.Utils.firstValid(options.format, 'zip_us5');break; 680 | case 'date': 681 | options.format = Spry.Widget.Utils.firstValid(options.format, 'mm/dd/yy'); 682 | break; 683 | case 'time': 684 | options.format = Spry.Widget.Utils.firstValid(options.format, 'HH:mm'); 685 | options.pattern = options.format.replace(/[hms]/gi, "0").replace(/TT/gi, 'AM').replace(/T/gi, 'A'); 686 | break; 687 | case 'ip': 688 | options.format = Spry.Widget.Utils.firstValid(options.format, 'ipv4'); 689 | options.characterMasking = Spry.Widget.ValidationTextField.ValidationDescriptors[this.type].characterMaskingFormats[options.format]; 690 | break; 691 | } 692 | 693 | //retrieve the validation type descriptor to be used with this instance (base on type and format) 694 | //widgets may have different validations depending on format (like zip_code with formats) 695 | var validationDescriptor = {}; 696 | if (options.format && Spry.Widget.ValidationTextField.ValidationDescriptors[this.type].formats) { 697 | if (Spry.Widget.ValidationTextField.ValidationDescriptors[this.type].formats[options.format]) { 698 | Spry.Widget.Utils.setOptions(validationDescriptor, Spry.Widget.ValidationTextField.ValidationDescriptors[this.type].formats[options.format]); 699 | } 700 | } else { 701 | Spry.Widget.Utils.setOptions(validationDescriptor, Spry.Widget.ValidationTextField.ValidationDescriptors[this.type]); 702 | } 703 | 704 | //set default values for some parameters which were not aspecified 705 | options.useCharacterMasking = Spry.Widget.Utils.firstValid(options.useCharacterMasking, false); 706 | options.hint = Spry.Widget.Utils.firstValid(options.hint, ''); 707 | options.isRequired = Spry.Widget.Utils.firstValid(options.isRequired, true); 708 | options.additionalError = Spry.Widget.Utils.firstValid(options.additionalError, false); 709 | if (options.additionalError) 710 | options.additionalError = this.getElement(options.additionalError); 711 | 712 | //set widget validation parameters 713 | //get values from validation type descriptor 714 | //use the user specified values, if defined 715 | options.characterMasking = Spry.Widget.Utils.firstValid(options.characterMasking, validationDescriptor.characterMasking); 716 | options.regExpFilter = Spry.Widget.Utils.firstValid(options.regExpFilter, validationDescriptor.regExpFilter); 717 | options.pattern = Spry.Widget.Utils.firstValid(options.pattern, validationDescriptor.pattern); 718 | options.validation = Spry.Widget.Utils.firstValid(options.validation, validationDescriptor.validation); 719 | if (typeof options.validation == 'string') { 720 | options.validation = eval(options.validation); 721 | } 722 | 723 | options.minValue = Spry.Widget.Utils.firstValid(options.minValue, validationDescriptor.minValue); 724 | options.maxValue = Spry.Widget.Utils.firstValid(options.maxValue, validationDescriptor.maxValue); 725 | 726 | options.minChars = Spry.Widget.Utils.firstValid(options.minChars, validationDescriptor.minChars); 727 | options.maxChars = Spry.Widget.Utils.firstValid(options.maxChars, validationDescriptor.maxChars); 728 | 729 | Spry.Widget.Utils.setOptions(this, options); 730 | Spry.Widget.Utils.setOptions(this.options, options); 731 | }; 732 | 733 | Spry.Widget.ValidationTextField.prototype.destroy = function() { 734 | if (this.event_handlers) 735 | for (var i=0; i this.maxChars) { 1005 | errors = errors | Spry.Widget.ValidationTextField.ERROR_CHARS_MAX; 1006 | continueValidations = false; 1007 | } 1008 | } 1009 | 1010 | //validation - testValue passes widget validation function 1011 | if (!mustRevert && this.validation && continueValidations) { 1012 | var value = this.validation(fixedValue, this.options); 1013 | if (false === value) { 1014 | errors = errors | Spry.Widget.ValidationTextField.ERROR_FORMAT; 1015 | continueValidations = false; 1016 | } else { 1017 | this.typedValue = value; 1018 | } 1019 | } 1020 | 1021 | if(!mustRevert && this.validation && this.minValue !== null && continueValidations) { 1022 | var minValue = this.validation(this.minValue.toString(), this.options); 1023 | if (minValue !== false) { 1024 | if (this.typedValue < minValue) { 1025 | errors = errors | Spry.Widget.ValidationTextField.ERROR_RANGE_MIN; 1026 | continueValidations = false; 1027 | } 1028 | } 1029 | } 1030 | 1031 | if(!mustRevert && this.validation && this.maxValue !== null && continueValidations) { 1032 | var maxValue = this.validation(this.maxValue.toString(), this.options); 1033 | if (maxValue !== false) { 1034 | if( this.typedValue > maxValue) { 1035 | errors = errors | Spry.Widget.ValidationTextField.ERROR_RANGE_MAX; 1036 | continueValidations = false; 1037 | } 1038 | } 1039 | } 1040 | 1041 | //an invalid value was tested; must make sure it does not get inside the input 1042 | if (this.useCharacterMasking && mustRevert) { 1043 | this.revertState(revertValue); 1044 | } 1045 | 1046 | this.errors = errors; 1047 | this.fixedValue = fixedValue; 1048 | 1049 | this.flags.locked = false; 1050 | 1051 | return mustRevert; 1052 | }; 1053 | 1054 | Spry.Widget.ValidationTextField.prototype.onChange = function(e) 1055 | { 1056 | if (Spry.is.opera && this.flags.operaRevertOnKeyUp) { 1057 | return true; 1058 | } 1059 | if (Spry.is.ie && e && e.propertyName != 'value') { 1060 | return true; 1061 | } 1062 | 1063 | if (this.flags.drop) { 1064 | //delay this if it's a drop operation 1065 | var self = this; 1066 | setTimeout(function() { 1067 | self.flags.drop = false; 1068 | self.onChange(null); 1069 | }, 0); 1070 | return; 1071 | } 1072 | 1073 | if (this.flags.hintOn) { 1074 | return true; 1075 | } 1076 | 1077 | if (this.keyCode == 8 || this.keyCode == 46 ) { 1078 | var mustRevert = this.doValidations(this.input.value, this.input.value); 1079 | this.oldValue = this.input.value; 1080 | if ((mustRevert || this.errors) && this.validateOn & Spry.Widget.ValidationTextField.ONCHANGE) { 1081 | var self = this; 1082 | setTimeout(function() {self.validate();}, 0); 1083 | return true; 1084 | } 1085 | } 1086 | 1087 | var mustRevert = this.doValidations(this.input.value, this.oldValue); 1088 | if ((!mustRevert || this.errors) && this.validateOn & Spry.Widget.ValidationTextField.ONCHANGE) { 1089 | var self = this; 1090 | setTimeout(function() {self.validate();}, 0); 1091 | } 1092 | return true; 1093 | }; 1094 | 1095 | Spry.Widget.ValidationTextField.prototype.onKeyUp = function(e) { 1096 | if (this.flags.operaRevertOnKeyUp) { 1097 | this.setValue(this.oldValue); 1098 | Spry.Widget.Utils.stopEvent(e); 1099 | this.selection.moveTo(this.selection.start, this.selection.start); 1100 | this.flags.operaRevertOnKeyUp = false; 1101 | return false; 1102 | } 1103 | if (this.flags.operaPasteOperation) { 1104 | window.clearInterval(this.flags.operaPasteOperation); 1105 | this.flags.operaPasteOperation = null; 1106 | } 1107 | }; 1108 | 1109 | Spry.Widget.ValidationTextField.prototype.operaPasteMonitor = function() { 1110 | if (this.input.value != this.oldValue) { 1111 | var mustRevert = this.doValidations(this.input.value, this.input.value); 1112 | if (mustRevert) { 1113 | this.setValue(this.oldValue); 1114 | this.selection.moveTo(this.selection.start, this.selection.start); 1115 | } else { 1116 | this.onChange(); 1117 | } 1118 | } 1119 | }; 1120 | 1121 | 1122 | Spry.Widget.ValidationTextField.prototype.compileDatePattern = function () 1123 | { 1124 | var dateValidationPatternString = ""; 1125 | var groupPatterns = []; 1126 | var fullGroupPatterns = []; 1127 | var autocompleteCharacters = []; 1128 | 1129 | 1130 | var formatRegExp = /^([mdy]+)([\.\-\/\\\s]+)([mdy]+)([\.\-\/\\\s]+)([mdy]+)$/i; 1131 | var formatGroups = this.options.format.match(formatRegExp); 1132 | if (formatGroups !== null) { 1133 | for (var i=1; i 0) { 1926 | this.range.setEndPoint("EndToEnd", ta_range); 1927 | } 1928 | } else if (this.element.nodeName == "INPUT"){ 1929 | this.range = this.element.ownerDocument.selection.createRange(); 1930 | this.range.move("character", -10000); 1931 | this.start = this.range.moveStart("character", start); 1932 | this.end = this.start + this.range.moveEnd("character", end - start); 1933 | } 1934 | this.range.select(); 1935 | } else { 1936 | this.start = start; 1937 | try { this.element.selectionStart = start;} catch(err) {} 1938 | this.end = end; 1939 | try { this.element.selectionEnd = end;} catch(err) {} 1940 | } 1941 | this.ignore = true; 1942 | this.update(); 1943 | }; 1944 | 1945 | Spry.Widget.SelectionDescriptor.prototype.moveEnd = function(amount) 1946 | { 1947 | if (Spry.is.ie && Spry.is.windows) { 1948 | this.range.moveEnd("character", amount); 1949 | this.range.select(); 1950 | } else { 1951 | try { this.element.selectionEnd++;} catch(err) {} 1952 | } 1953 | this.update(); 1954 | }; 1955 | 1956 | Spry.Widget.SelectionDescriptor.prototype.collapse = function(begin) 1957 | { 1958 | if (Spry.is.ie && Spry.is.windows) { 1959 | this.range = this.element.ownerDocument.selection.createRange(); 1960 | this.range.collapse(begin); 1961 | this.range.select(); 1962 | } else { 1963 | if (begin) { 1964 | try { this.element.selectionEnd = this.element.selectionStart;} catch(err) {} 1965 | } else { 1966 | try { this.element.selectionStart = this.element.selectionEnd;} catch(err) {} 1967 | } 1968 | } 1969 | 1970 | this.update(); 1971 | }; 1972 | 1973 | ////////////////////////////////////////////////////////////////////// 1974 | // 1975 | // Spry.Widget.Form - common for all widgets 1976 | // 1977 | ////////////////////////////////////////////////////////////////////// 1978 | 1979 | if (!Spry.Widget.Form) Spry.Widget.Form = {}; 1980 | if (!Spry.Widget.Form.onSubmitWidgetQueue) Spry.Widget.Form.onSubmitWidgetQueue = []; 1981 | 1982 | if (!Spry.Widget.Form.validate) { 1983 | Spry.Widget.Form.validate = function(vform) { 1984 | var isValid = true; 1985 | var isElementValid = true; 1986 | var q = Spry.Widget.Form.onSubmitWidgetQueue; 1987 | var qlen = q.length; 1988 | for (var i = 0; i < qlen; i++) { 1989 | if (!q[i].isDisabled() && q[i].form == vform) { 1990 | isElementValid = q[i].validate(); 1991 | isValid = isElementValid && isValid; 1992 | } 1993 | } 1994 | return isValid; 1995 | } 1996 | }; 1997 | 1998 | if (!Spry.Widget.Form.onSubmit) { 1999 | Spry.Widget.Form.onSubmit = function(e, form) 2000 | { 2001 | if (Spry.Widget.Form.validate(form) == false) { 2002 | return false; 2003 | } 2004 | return true; 2005 | }; 2006 | }; 2007 | 2008 | if (!Spry.Widget.Form.onReset) { 2009 | Spry.Widget.Form.onReset = function(e, vform) 2010 | { 2011 | var q = Spry.Widget.Form.onSubmitWidgetQueue; 2012 | var qlen = q.length; 2013 | for (var i = 0; i < qlen; i++) { 2014 | if (!q[i].isDisabled() && q[i].form == vform && typeof(q[i].reset) == 'function') { 2015 | q[i].reset(); 2016 | } 2017 | } 2018 | return true; 2019 | }; 2020 | }; 2021 | 2022 | if (!Spry.Widget.Form.destroy) { 2023 | Spry.Widget.Form.destroy = function(form) 2024 | { 2025 | var q = Spry.Widget.Form.onSubmitWidgetQueue; 2026 | for (var i = 0; i < Spry.Widget.Form.onSubmitWidgetQueue.length; i++) { 2027 | if (q[i].form == form && typeof(q[i].destroy) == 'function') { 2028 | q[i].destroy(); 2029 | i--; 2030 | } 2031 | } 2032 | } 2033 | }; 2034 | 2035 | if (!Spry.Widget.Form.destroyAll) { 2036 | Spry.Widget.Form.destroyAll = function() 2037 | { 2038 | var q = Spry.Widget.Form.onSubmitWidgetQueue; 2039 | for (var i = 0; i < Spry.Widget.Form.onSubmitWidgetQueue.length; i++) { 2040 | if (typeof(q[i].destroy) == 'function') { 2041 | q[i].destroy(); 2042 | i--; 2043 | } 2044 | } 2045 | } 2046 | }; 2047 | 2048 | ////////////////////////////////////////////////////////////////////// 2049 | // 2050 | // Spry.Widget.Utils 2051 | // 2052 | ////////////////////////////////////////////////////////////////////// 2053 | 2054 | if (!Spry.Widget.Utils) Spry.Widget.Utils = {}; 2055 | 2056 | Spry.Widget.Utils.punycode_constants = { 2057 | base : 36, tmin : 1, tmax : 26, skew : 38, damp : 700, 2058 | initial_bias : 72, initial_n : 0x80, delimiter : 0x2D, 2059 | maxint : 2<<26-1 2060 | }; 2061 | 2062 | Spry.Widget.Utils.punycode_encode_digit = function (d) { 2063 | return String.fromCharCode(d + 22 + 75 * (d < 26)); 2064 | }; 2065 | 2066 | Spry.Widget.Utils.punycode_adapt = function (delta, numpoints, firsttime) { 2067 | delta = firsttime ? delta / this.punycode_constants.damp : delta >> 1; 2068 | delta += delta / numpoints; 2069 | 2070 | for (var k = 0; delta > ((this.punycode_constants.base - this.punycode_constants.tmin) * this.punycode_constants.tmax) / 2; k += this.punycode_constants.base) { 2071 | delta /= this.punycode_constants.base - this.punycode_constants.tmin; 2072 | } 2073 | return k + (this.punycode_constants.base - this.punycode_constants.tmin + 1) * delta / (delta + this.punycode_constants.skew); 2074 | }; 2075 | 2076 | /** 2077 | * returns a Punicode representation of a UTF-8 string 2078 | * adapted from http://tools.ietf.org/html/rfc3492 2079 | */ 2080 | Spry.Widget.Utils.punycode_encode = function (input, max_out) { 2081 | var inputc = input.split(""); 2082 | input = []; 2083 | for(var i=0; i 0) { 2107 | output += String.fromCharCode(this.punycode_constants.delimiter); 2108 | out++; 2109 | } 2110 | 2111 | while (h < input_len) { 2112 | for (m = this.punycode_constants.maxint, j = 0; j < input_len; j++) { 2113 | if (input[j] >= n && input[j] < m) { 2114 | m = input[j]; 2115 | } 2116 | } 2117 | if (m - n > (this.punycode_constants.maxint - delta) / (h + 1)) { 2118 | return false; 2119 | } 2120 | 2121 | delta += (m - n) * (h + 1); 2122 | n = m; 2123 | 2124 | for (j = 0; j < input_len; j++) { 2125 | if (input[j] < n ) { 2126 | if (++delta == 0) { 2127 | return false; 2128 | } 2129 | } 2130 | 2131 | if (input[j] == n) { 2132 | for (q = delta, k = this.punycode_constants.base; true; k += this.punycode_constants.base) { 2133 | if (out >= max_out) { 2134 | return false; 2135 | } 2136 | 2137 | t = k <= bias ? this.punycode_constants.tmin : k >= bias + this.punycode_constants.tmax ? this.punycode_constants.tmax : k - bias; 2138 | if (q < t) { 2139 | break; 2140 | } 2141 | 2142 | output += this.punycode_encode_digit(t + (q - t) % (this.punycode_constants.base - t)); 2143 | out++; 2144 | q = (q - t) / (this.punycode_constants.base - t); 2145 | } 2146 | 2147 | output += this.punycode_encode_digit(q); 2148 | out++; 2149 | bias = this.punycode_adapt(delta, h + 1, h == b); 2150 | delta = 0; 2151 | h++; 2152 | } 2153 | } 2154 | delta++, n++; 2155 | } 2156 | 2157 | return output; 2158 | }; 2159 | 2160 | Spry.Widget.Utils.setOptions = function(obj, optionsObj, ignoreUndefinedProps) 2161 | { 2162 | if (!optionsObj) 2163 | return; 2164 | for (var optionName in optionsObj) 2165 | { 2166 | if (ignoreUndefinedProps && optionsObj[optionName] == undefined) 2167 | continue; 2168 | obj[optionName] = optionsObj[optionName]; 2169 | } 2170 | }; 2171 | 2172 | Spry.Widget.Utils.firstValid = function() { 2173 | var ret = null; 2174 | for(var i=0; i 2 | 12 | 13 | 14 | 15 | 16 | Web FAX for Asterisk 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |

Web FAX for Asterisk

26 |

27 | 62 | asterisk ALL=(ALL) NOPASSWD: /usr/bin/wvPDF (for whatever the apache user is) 88 | 89 | $wv_command = "sudo /usr/bin/wvPDF $input_file_doc $input_file" ; 90 | // echo "
executing : ". $wv_command . "
\n"; 91 | $wv_command_output = system($wv_command, $retval); 92 | 93 | // echo $wv_command_output; 94 | 95 | if ($retval != 0) { 96 | echo "There was an error converting your DOC file to PDF. Try uploading the file again or with an older version of PDF"; 97 | $error = $ERROR_CONVERTING_DOCUMENT; 98 | $doc_convert_output = $wv_command_output; 99 | // die(); 100 | }else{ 101 | // set the input file type to .pdf now as it's converted 102 | $input_file_type = "pdf"; 103 | } 104 | } else{ 105 | echo "There was an error uploading the file, please try again!"; 106 | } 107 | } // END DOC file detected 108 | 109 | 110 | // IF it was originally a PDF 111 | if ($ext == "pdf") { 112 | if(move_uploaded_file($_FILES['faxFile']['tmp_name'], $input_file)) { 113 | $input_file_type = "pdf"; 114 | }else{ 115 | echo "There was an error uploading the file, please try again!"; 116 | } 117 | } 118 | // we should now have a PDF file which we will convert to tif 119 | 120 | if($error == $ERROR_NO_ERROR && $input_file_type == "pdf") { 121 | 122 | // convert the attached PDF to .tif using ghostsccript ... 123 | $gs_command = "gs -q -dNOPAUSE -dBATCH -dSAFER -sDEVICE=tiffg3 -sOutputFile=${input_file_tif} -f $input_file " ; 124 | $gs_command_output = system($gs_command, $retval); 125 | $doc_convert_output = $gs_command_output; 126 | 127 | if ($retval != 0) { 128 | echo "There was an error converting your PDF file to TIF. Try uploading the file again or with an older version of PDF"; 129 | $error = $ERROR_CONVERTING_DOCUMENT; 130 | // die(); 131 | } 132 | else { 133 | 134 | // call the faxout.pl script to create a call file and copy the required files to appropriate directories 135 | // ------------------------------------------------------------------------------------------------------ 136 | // $script_local_path = $_REAL_BASE_DIR = realpath(dirname(__FILE__)); 137 | 138 | 139 | 140 | $faxHeader = $_POST["faxHeader"]; 141 | $localID = $_POST["localID"]; 142 | $email = $_POST["email"]; 143 | $dest = $_POST["dest"]; 144 | 145 | //echo "Sending FAX. Debug information:
\n"; 146 | //echo " -- faxHeader: $faxHeader
\n"; 147 | //echo " -- localID: $localID
\n"; 148 | //echo " -- email: $email
\n"; 149 | //echo " -- dest: $dest
\n"; 150 | 151 | // ----------------------- PERL SCRIPT ------- NOT USING FORM NOW ------------------------------------------\\ 152 | // setting up the options required by faxout.pl 153 | // $faxout_command = $script_local_path . "/faxout.pl"; 154 | 155 | 156 | // calling faxout.pl now 157 | // exec($faxout_command, $faxout_output, $retval); 158 | 159 | // echo $retval . "
\n"; // should be 0 for correct output by faxout.pl 160 | // echo $faxout_output; 161 | 162 | // END call faxout.pl 163 | // ------------------------------------------------------------------------------------------------------ 164 | 165 | // ----------------------- END PERL SCRIPT ------- NOT USING FORM NOW ------------------------------------------\\ 166 | 167 | // ------------------------------ CREATING CALL FILE AND SENDING THROUGH PHP ------------------------------- 168 | 169 | $callfile = "Channel: Local/$dest$outbound_route\n" . 170 | "MaxRetries: 1\n" . 171 | "RetryTime: 60\n" . 172 | "WaitTime: 60\n" . 173 | "Archive: yes\n" . 174 | "Context: $outboundfax_context \n" . 175 | "Extension: s\n" . 176 | "Priority: 1\n" . 177 | "Set: FAXFILE=$input_file_tif\n" . 178 | "Set: FAXHEADER=$faxHeader\n" . 179 | "Set: TIMESTAMP=" . date("d/m/y : H:i:s",time()) . "\n" . 180 | "Set: DESTINATION=$dest\n". 181 | "Set: LOCALID=$localID\n" . 182 | "Set: EMAIL=$email\n"; 183 | 184 | // echo $callfile; 185 | // create the call file in /tmp 186 | $callfilename = unique_name("/tmp", ".call"); 187 | $f = fopen($callfilename, "w"); 188 | fwrite($f, $callfile); 189 | fclose($f); 190 | 191 | // move the file to asterisk outgoing spool directory 192 | // stopping the call to asterisk for now .. TODO: uncomment before deploying .. 193 | rename($callfilename, $asterisk_spool_folder . "/" . substr($callfilename,4)); 194 | 195 | //------------------------- END CREATE CALL FILE ----------------------------------------------------------- 196 | } 197 | } 198 | // if no error, display that notification will be sent. 199 | 200 | ?> 201 | END HTML HACK to supress errors appearing on screen. 202 | --> 203 | 204 | 205 | Your fax document could not be converted. Please try again or upload the document ". 213 | " in another format. The error details follow.

". 214 | " $doc_convert_output "; 215 | } 216 | 217 | ?> 218 | 219 | 220 |

221 |
222 |
223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /sendfaxform.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | Web FAX for Asterisk 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 |

Web FAX for Asterisk

27 |

Please use the form below to input the required information and click on "Send FAX" below. You will receive a notification about the status of your fax through the email you provide below. 28 | 29 |

30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 59 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
FAX Header 34 | Type the header text you want to appear at the top of your FAX.
Local Station Identifier 40 | 41 |
42 | This field is required.Invalid format.
Enter the number that should be used as the sender's FAX number.
Notification Email Address 48 | 49 |
50 | This field is required.Invalid format.
Enter the email to which FAX notifications would be sent. If you don't receive any email on this address after sending the FAX, it means we probably don't have access to the destination number. For example, if you are trying to send a FAX to Europe when we only have trunks for US/Canada.
FAX Destination 56 | 57 |
58 | This field is required.Invalid format.
Destination FAX number. Please do not include any spaces or special characters.
Attach file 64 | Filetypes acceptable: DOC , PDF
  
73 |

74 |

 

75 |
76 | 77 |

 

78 |
79 |
80 | 81 | 82 | 87 | 88 | 89 | --------------------------------------------------------------------------------