Plugin Directory

Changeset 728099


Ignore:
Timestamp:
06/18/2013 11:35:41 PM (13 years ago)
Author:
DrandLomB
Message:

=Check in v0.95

Location:
dlbs-send-a-link/trunk
Files:
1 added
6 edited

Legend:

Unmodified
Added
Removed
  • dlbs-send-a-link/trunk/dsl-page.html

    r726535 r728099  
    22 * Page template for dlb's Send-A-Link (dsl)
    33 *
    4  * This file is based on page.php from the theme folder.
     4 * This file should be based on the structure seen in file page.php from the theme folder.
    55 *
    66 * It includes the form in one div and the confirmation in a separate div.
    7  * The confirm div will initially be hidden with style display:none;.
    8  * Javascript later shows the confirm with style display:block; as it hides
    9  * the form with style display:none;.
     7 * The confirm div is initially hidden with style display:none;.
     8 * Later it is shown with style display:block as the form is hidden with style display:none;.
    109 *
    11  *  This file is used by dsl.php, and is expected to be found
    12  *  in the same folder as dsl.php. To locate it elsewhere, change
    13  *  the DSL_OTHER_TEMPLATES definition in the configuration section of dsl.php.
     10 * This file is used by dsl.php, and is expected to be found
     11 * in the same folder as dsl.php.
    1412 *
    15  * Placeholders available are as follows. To use, they must be enclosed in #'s:
     13 * The following placeholders are available. To use, they must be enclosed in #'s:
    1614 *  - dsl-post-title
    1715 *  - dsl-recipient-name
     
    3937    <div id="content">
    4038<!--*****************************************************
    41  *  The form template starts here
     39 *  The form template starts here.
     40    All the hidden fields are grouped in the first paragraph.
     41    The unusual label on the hidden "dsl_pid" field is to handle a general form-level error message.
     42    The empty spans in the labels are to accept error messages specific to the corresponding inputs.
     43    Although text can be customized, the arrangement of the markup is important,
     44    including the order of attributes within the tags.
    4245-->
    4346        <div class="post" id="dsl-form-div">
     
    4851                <form id="dsl-form" action="" method="post" name="dsl-form" onsubmit="dslSend(this); return false;">
    4952                    <p>
    50                         <input type="hidden" name="dsl_nonce",          value="#wp-nonce#"       />
    51                         <input type="hidden" name="dsl_pid"             value="#dsl-post-id#"    />
    52                         <input type="hidden" name="dsl_secure",         value=""                 />
    53                         <input type="hidden" name="dsl_captcha_random"  value="#captcha-random#" />
    54                         <input type="hidden" name="dsl_back",           value="#dsl-back-link#"  />
     53                       <input name="dsl_nonce"          type="hidden"   value="#wp-nonce#"       />
     54                       <input name="dsl_secure"         type="hidden"   value=""                 />
     55                       <input name="dsl_captcha_random" type="hidden"   value="#captcha-random#" />
     56                       <input name="dsl_back"           type="hidden"   value="#dsl-back-link#"  />
     57                       <label  for="dsl_pid"                       ><span class="dsl-error-field"></span></label>
     58                      <input  name="dsl_pid"            type="hidden"   value="#dsl-post-id#"    />
    5559                    </p>
    56                     <p><label  for="dsl_rname"    >Recipient's name:</label><br />
    57                       <input  name="dsl_rname"                                              type="text" maxlength="#max-name-chars#" /></p>
    58                     <p><label  for="dsl_raddress" >Recipient's email address:</label><br />
    59                       <input  name="dsl_raddress"                                           type="text" /></p>
    60                     <p><label  for="dsl_sname"    >Your name:</label><br />
    61                       <input  name="dsl_sname"                                              type="text" maxlength="#max-name-chars#" /></p>
    62                     <p><label  for="dsl_saddress" >Your email address:</label><br />
    63                       <input  name="dsl_saddress"                                           type="text" /></p>
    64                     <p><label  for="dsl_comments" >Your comments (optional):</label><br />
    65                     <textarea name="dsl_comments"                                                       maxlength="#max-comment-chars#" /></textarea></p>
     60                    <p><label  for="dsl_rname"    >Recipient's name:<span class="dsl-error-field"></span></label><br />
     61                      <input  name="dsl_rname"          type="text"     maxlength="#max-name-chars#" /></p>
     62                    <p><label  for="dsl_raddress" >Recipient's email address:<span class="dsl-error-field"></span></label><br />
     63                      <input  name="dsl_raddress"       type="text" /></p>
     64                    <p><label  for="dsl_sname"    >Your name:<span class="dsl-error-field"></span></label><br />
     65                      <input  name="dsl_sname"          type="text"     maxlength="#max-name-chars#" /></p>
     66                    <p><label  for="dsl_saddress" >Your email address:<span class="dsl-error-field"></span></label><br />
     67                      <input  name="dsl_saddress"       type="text" /></p>
     68                    <p><label  for="dsl_comments" >Your comments (optional):<span class="dsl-error-field"></span></label><br />
     69                    <textarea name="dsl_comments"                       maxlength="#max-comment-chars#" /></textarea></p>
    6670                    <div id="dsl-captcha">
    67                     <p><label  for="dsl_captcha"  >Security check:</label></p>
    68                     <div>
    69                         #captcha-image#
    70                        <input name="dsl_captcha"                                            type="text" autocomplete="off" value="" />
     71                    <p><label  for="dsl_captcha"  >Security check:<span class="dsl-error-field"></span></label></p>
     72                    <div>#captcha-image#
     73                       <input name="dsl_captcha"        type="text"     value="" autocomplete="off" />
    7174                        <br />
    7275                        <a href="javascript:captchas_image_reload('captchas.net')">Reload</a>
     
    7578                    </div>
    7679                    </div>
    77                     <p class="button"><input type="submit" name="dsl_submit" value="#dsl-submit-button#" /></p>
     80                    <p class="button">
     81                       <label  for="dsl_submit"><span class="dsl-error-field"></span></label>
     82                      <input  name="dsl_submit"         type="submit"   value="#dsl-submit-button#" /></p>
    7883                </form>
    7984                <p>Or, you can return to <a href="#dsl-back-link#">the article page</a> without sending a link.</p>
     
    8287        </div>
    8388        <script type="text/javascript">
    84             // this allows the tab key to go from the comments field to the captcha entry field without stopping on the captcha image
     89            // this Javascript statement llows the tab key to go from the comments field to the captcha entry field without stopping on the captcha image
    8590            document.getElementById('dsl-captcha').getElementsByTagName('div')[0].getElementsByTagName('a')[0].tabIndex=10;
    8691        </script>
    8792<!--*****************************************************
    88  *  The confirmation page template starts here
     93 * The confirmation page template starts here
    8994 *
    90  *  Javascript will insert the confirmation message
    91  *  it receives into div.postentry
     95 * This is just an empty div that gets filled in later
     96 * with the contents of the email message sent.
    9297-->
    9398        <div class="post" id="dsl-confirm-div">
  • dlbs-send-a-link/trunk/dsl-templates.html

    r726535 r728099  
    11<!--*****************************************************
    2  *  Templates for dlb's Send-A-Link (dsl)
     2 *  Message and confirmation templates for dlb's Send-A-Link (dsl)
    33 *
    44 *  Contains templates in separate sections for:
    5  *  - Email send error message
    65 *  - Confirmation page
    7  *  - Confirmation page navigation links
    86 *  - Email message
    97 *
    108 *  This file is used by dsl.php, and is expected to be found
    11  *  in the same folder as dsl.php. To locate it elsewhere, change
    12  *  the DSL_PAGE_TEMPLATE definition in the configuration section of dsl.php.
     9 *  in the same folder as dsl.php.
    1310 *
    14  * Placeholders available are as follows. To use, they must be enclosed in #'s:
     11 * The following placeholders are available. To use, they must be enclosed in #'s:
    1512 *  - dsl-post-title
    1613 *  - dsl-recipient-name
     
    3431-->
    3532<!--*****************************************************
    36  * Error template is shown if an unexplained error occurs
    37  * while sending the email message
    38 -->
    39 <error>
    40     <p>
    41         Unfortunately, an error occurred while attempting to send the message.<br />
    42         We apologize for the inconvenience and appreciate your patience.<br />
    43         Please try again.
    44     </p>
    45 </error>
    46 <!--*****************************************************
    47  * Confirm template is shown after successfully sending email
     33 * Confirm template:
     34 * shown after successfully sending email
    4835-->
    4936<confirm>
    5037    <p>
    5138        Here is a copy of the message that was sent to
    52         #dsl-recipient-name# (#dsl-recipient-address#):
     39        #dsl-recipient-name# at (#dsl-recipient-address#):
    5340    </p>
    5441    <div class="message">
     
    5643        <p>#dsl-message-body#</p>
    5744    </div>
    58 </confirm>
    59 <!--*****************************************************
    60  * Nav template is shown after both the error and confirm templates
    61 -->
    62 <nav>
    6345    <p>
    6446        Now, you can <a href="" onclick="location.reload(); return false;">send another message</a>,<br />
    6547        or, you can return to <a href="#dsl-back-link#">the article page</a>.
    6648    </p>
    67 </nav>
     49</confirm>
    6850<!--*****************************************************
    69  * Message template is used to construct the email message
     51 * Message template:
     52 * used to construct the email message
    7053-->
    7154<message>
     
    9073    </html>
    9174</message>
    92 <!--*****************************************************
    93  * Error alert template is shown to tell user about errors
    94 -->
    95 <error-alert>
    96 Please correct the highlighted error(s) and #dsl-submit-button# again
    97 </error-alert>
  • dlbs-send-a-link/trunk/dsl.css

    r726535 r728099  
    66 * to fit within 320 px wide.
    77 * This is currently expressed in pixels, but
    8  * will be recast as rem or pct or em later.
     8 * should be re-specified as a pct of the enclosing container.
    99 *
    10  *  This file is used by dsl.php, and is expected to be found
    11  *  in the same folder as dsl.php. To locate it elsewhere, change
    12  *  the DSL_CSS_FILE definition in the configuration section of dsl.php.
     10 * This file is used by dsl.php, and is expected to be found
     11 * in the same folder as dsl.php.
     12 *
     13 * Additional styles to modify or override these can be
     14 * specified in a file of this same name located in the theme folder.
    1315 *
    1416 * See http://www.ajaxload.info/ to make a
     
    7981}
    8082#dsl-captcha input {
    81     width:160px;
     83    width:9em;
    8284    margin:15px 0;
    8385}
  • dlbs-send-a-link/trunk/dsl.js

    r726535 r728099  
    22 *  Javascript for dlb's Send-A-Link (dsl)
    33 *
    4  *  Primarily handles AJAX communication of user input and
    5  *  server response to eliminate need for re-sending entire page.
     4 *  Handles AJAX communication of user input and
     5 *  server responses to eliminate need for re-sending entire page.
    66 *
    77 *  This file is used by dsl.php, and is expected to be found
    8  *  in the same folder as dsl.php. To locate it elsewhere, change
    9  *  the DSL_JAVASCRIPT_FILE definition in the configuration section of dsl.php.
     8 *  in the same folder as dsl.php.
    109 *
    1110 *  This is written with plenty of white-space and comments for clarity,
     
    2423    // Create AJAX object
    2524    // See explanation of AJAX setup at http://www.tizag.com/ajaxTutorial/ajaxform.php
    26     var ajax, params, testurl, realurl, url, out;
     25    var ajax, params;
    2726    try{
    2827        ajax = new XMLHttpRequest(); // Opera 8.0+, Firefox, Safari
     
    4443    ajax.onreadystatechange = function(){
    4544        if(ajax.readyState == 4){ // indicates that response has been completely received from the server
    46             // Send the form and the server's response to the function that does the work
     45            // pass control to the function that does the work
    4746            dslProcessResponse(form, ajax.response);
    4847        }
    4948    }
    50     // Format the form input for sending to server as a POST request
     49    // Collect inputs from the form input for sending to server as a POST request
    5150    // Note that the global variable ajaxData was provided by the server via a wp_localize_script call
    5251    params = 'action=' + ajaxData.action + '&' + dslGetFormData(form);
    53     // Establish type of request (POST vs GET), and the URL to which the request will be sent,
    54     // and headers to tell data format
     52    // Establish type of request (i.e., using POST, not GET) and the URL
    5553    ajax.open("POST", ajaxData.url, true);
     54    // Establish headers to tell data type
    5655    ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
    5756    // Send request to the server
     
    7372    var i, params='';
    7473    // loop through all elements of the form
     74    // this simple routine works for text inputs
     75    // but would have to be extended to handle checkboxes, radios, etc.
    7576    for(i=0; i<form.elements.length; i++){
    7677        if(form.elements[i].name && form.elements[i].value) {
     
    9394    // convert the response from a JSON string to javascript objects
    9495    response = JSON.parse(response);
    95     // check if a 2nd nonce was received that says CAPTCHA was previously satisfied,
    96     // and if so, store that nonce it in a form element, and hide the captcha div
    97     if (typeof response.secure != 'undefined') {
    98         form.elements["dsl_secure"].value = response.secure;
    99         document.getElementById('dsl-captcha').style.display = 'none';
    100     }
    10196    // Either show errors or show confirmation
    102     if (response.reset) {
     97    if (typeof response.reset != 'undefined') {
    10398        // server says to reset the page
    104         dslDoReset();
     99        // do so showing the form-level error message
     100        dslDoReset(response.errors['dsl_pid']);
    105101    } else if (typeof response.errors != 'undefined') {
    106102        // got errors
    107103        // show the messages
    108104        dslShowErrors(form, response.errors);
     105        // check if a 2nd nonce was received that says CAPTCHA was previously satisfied,
     106        // and if so, store that nonce it in a form element, and hide the captcha div
     107        if (typeof response.secure != 'undefined') {
     108            form.elements["dsl_secure"].value = response.secure;
     109            document.getElementById('dsl-captcha').style.display = 'none';
     110        }
    109111        // scroll top of form into view
    110112        document.getElementById('dsl-form-div').scrollIntoView();
     
    119121        // then select all of the DIVs within it via .getElementsByTagName,
    120122        // then index to the first of them via [0]
    121         // then access property innerHTML,
    122         // and set it equal to the formatted response
     123        // then access property innerHTML, and
     124        // load the formatted response from server into it
    123125        cDiv.getElementsByTagName('DIV')[0].innerHTML = response.confirms.body;
    124         // show the confirmation div
     126        // show the confirm div
    125127        cDiv.style.display = 'block';
    126128        // scroll top of page into view
    127         document.getElementById('page').scrollIntoView();
     129        document.getElementsByTagName('body')[0].scrollIntoView();
    128130    } else {
    129         // unexplained error, so reset
    130         dslDoReset();
     131        // unexplained error, so reset using error message from the global sent by the server
     132        dslDoReset(ajaxData.errorGeneral);
    131133    }
    132134}
     
    136138 * Displays an alert for a processing error and reloads the page
    137139 */
    138 function dslDoReset() {
     140function dslDoReset(alertMsg) {
    139141    // display message for a processing error
    140     alert ('A general processing error occurred.\n\rWe apologize for the inconvenience and appreciate your patience.\n\rPlease try again.');
     142    alert (alertMsg);
    141143    // force a page reload from the server
    142144    document.location.reload(true);
     145}
     146/********************************************************
     147 * dslShowErrors()
     148 *
     149 * Shows each error message in the label element of the associated input element.
     150 * Expects that the errors object contains "key":value pairs,
     151 * where key is the name given to each input element.
     152 */
     153function dslShowErrors(form, errors){
     154    var field, spans;
     155    // assocate labels to elements
     156    dslAssociateLabels(form);
     157    // loop through all of the error messages
     158    for (field in errors){
     159        // use the "key" for this error to select the form element
     160        // then access its label attribute,
     161        // then get all the spans within it
     162        // then find one with the right class
     163        // and put the error message into its innerHTML
     164        spans = form.elements[field].label.getElementsByTagName('span');
     165        for (i=0; i<spans.length; i++) {
     166            if(spans[i].className == 'dsl-error-field') {
     167                spans[i].innerHTML = '<br />' + errors[field];
     168                break;
     169            }
     170        }
     171    }
    143172}
    144173/********************************************************
     
    164193}
    165194/********************************************************
    166  * dslShowErrors()
    167  *
    168  * Shows each error message in the label element of the associated input element.
    169  * Expects that the errors object contains "key":value pairs,
    170  * where key is the name given to each input element.
    171  */
    172 function dslShowErrors(form, errors){
    173     var field;
    174     // assocate labels to elements
    175     dslAssociateLabels(form);
    176     // loop through all of the error messages
    177     for (field in errors){
    178         // use the "key" for this error to select the form element
    179         // then access its label attribute,
    180         // and set its innerHTML property to the error message
    181         // the spans around the error message allow it to be identified for deletion
    182         form.elements[field].label.innerHTML += '<span class="dsl-error-field"><br />' + errors[field] + '</span>';
    183     }
    184 }
    185 /********************************************************
    186195 * dslClearErrors()
    187196 *
     
    193202    // select all the span elements on the form
    194203    errors = form.getElementsByTagName('SPAN');
    195     // loop thru them in reverse order so that removal of one doesn't renumber the rest
    196     for (i=errors.length-1; i>=0; i--){
     204    // Loop thru them
     205    for (i=0; i<errors.length; i++){
    197206        // check each span to see if it is an error message
    198207        if(errors[i].className == 'dsl-error-field') {
    199             // this one is, so delete it by accessing it's parent
    200             // and using the remove method
    201             errors[i].parentNode.removeChild(errors[i]);
    202         }
    203     }
    204 }
     208            // this is one so clear its value
     209            errors[i].innerHTML = '';
     210        }
     211    }
     212}
  • dlbs-send-a-link/trunk/dsl.php

    r726856 r728099  
    33Plugin Name: dlb's Send-A-Link
    44Plugin URI: http://wordpress.org/plugins/dlbs-send-a-link/
    5 Description: Send-A-Link allows visitors to send someone an email containing a link to the post or page and some comments.
    6 Version: 1.0
     5Description: dlb's Send-A-Link allows visitors to send someone an email containing a link to the post or page.
     6Version: 0.95
    77Author: Dave Bezaire
    88Author URI: http://davebezaire.com/
     
    2727 * Globals and constants that can be customized
    2828 */
     29    // Input validation: maximum number of characters in the comments
     30    define ( 'DSL_MAX_COMMENT_CHARS', 250);
     31    // Input validation: minimum number of characters in the name
     32    define ( 'DSL_MIN_NAME_CHARS', 1);
     33    // Input validation: maximum number of characters in the name
     34    define ( 'DSL_MAX_NAME_CHARS', 30);
     35    // Input validation: minimum interval between sends in seconds
     36    define ( 'DSL_MIN_SEND_INTERVAL', 30 );
     37    // The string shown in DSL_SUBMIT_BUTTON_VALUE is
     38    // shown on and returned by the form's submit button.
     39    // This is a global because it is a trigger used in an IF statement.
     40    // Changing it in this definition will automatically insert it into the form template
     41    // and in the corresponding IF statement so that changing it
     42    // will not require any other alteration in the program.
     43    // It is also shown in the DSL_ERROR_ALERT message.
     44    define ( 'DSL_SUBMIT_BUTTON_VALUE', 'Send it!' );
     45    // Alert displayed to tell user that an error needs attention
     46    define ( 'DSL_ERROR_ALERT', 'Please correct the highlighted error(s) and ' . DSL_SUBMIT_BUTTON_VALUE . ' again' );
     47    // Message for an unexplained error
     48    define ( 'DSL_GENERAL_ERROR_MESSAGE', 'Unfortunately, a general processing error occurred. We apologize for the inconvenience and appreciate your patience. Please try again.' );
     49    // Message for an unexplained error while sending mail
     50    define ( 'DSL_SENDING_ERROR_MESSAGE', 'Unfortunately, an error occurred while attempting to send the message. We apologize for the inconvenience and appreciate your patience. Please try again.' );
     51    // Message for exceeding maximum characters
     52    define ( 'DSL_MAX_CHARS_ERROR', 'Must be no more than %d characters' );
     53    // Message for too few characters
     54    define ( 'DSL_MIN_CHARS_ERROR', 'Must be at least %d character(s)' );
     55    // Message for invalid email address
     56    define ( 'DSL_INVALID_EMAIL_ERROR', 'Must be a valid email address' );
     57    // Message for invalid characters in email address
     58    define ( 'DSL_INVALID_EMAIL_CHARS', 'May not contain invalid characters' );
     59    // Message for invalid characters in name
     60    define ( 'DSL_INVALID_NAME_CHARS', 'Must contain only: A-Z, a-z, 0-9, . , _ - " \' %' );
     61    // Message for invalid characters in comments
     62    define ( 'DSL_INVALID_COMMENTS_CHARS', 'Must not contain invalid characters (only limited HTML is allowed)' );
     63    // Message for invalid nonce
     64    define ( 'DSL_INVALID_NONCE', 'Security check failed' );
     65    // Message for incorrect CAPTCHA response
     66    define ( 'DSL_INCORRECT_CAPTCHA_ERROR', 'Must match characters in image' );
     67    // Message for internal CAPTCHA error
     68    define ( 'DSL_INTERNAL_CAPTCHA_ERROR', 'Internal captcha error' );
     69    // Message for violating minimum sending interval
     70    define ( 'DSL_MIN_SEND_INTERVAL_MESSAGE', 'Must wait at least %d seconds between sends<br />' );
     71    // Message for invalid show value
     72    define ( 'DSL_INVALID_SHOW_VALUE', 'Invalid value for show in dslLink' );
     73    // Default text for link
     74    define ( 'DSL_DEFAULT_LINK_TEXT', ' Send a link to this article' );
     75    // Default link icon file name
     76    define ( 'DSL_DEFAULT_ICON_FILE', 'email.gif' );
     77/********************************************************
     78 * Globals and constants that should not be changed
     79 */
     80    //Global data array used throughout
     81    $dslData = array();
    2982    // The following four definitions use two pairs of WordPress functions
    3083    // to produce references to the folder in which this plugin file is located,
     
    3891        // URL to the theme folder is used to load scripts and styles
    3992        define ( 'DSL_THEME_URL', get_stylesheet_directory_uri() . '/' );
    40     // We only look for the JS file in the plugin folder; it can not be empty
    41     define ( 'DSL_JAVASCRIPT_FILE', 'dsl.js' );
    42     // The following three definitions specify the names of the customization files.
     93    // The following definitions specify the names of the customization files.
    4394    // By default they are located relative to the plugin folder,
    44     // but they can be overridden by a copy in theme folder.
     95    // but they can be overridden by a copy in the theme folder.
     96        // filename for the captcha configuration parameters
     97        define ( 'DSL_CAPTCHA_CONFIG', 'dsl-captcha.php' );
    4598        // filename for form/confirm page template
    4699        define ( 'DSL_PAGE_TEMPLATE', 'dsl-page.html' );
    47100        // filename for other templates
    48101        define ( 'DSL_OTHER_TEMPLATES', 'dsl-templates.html' );
    49         // filename of styles; the can be the empty string (i.e. '') which might be appropriate
    50         // if the styles are included in the themes's CSS file
     102        // filename for CSS styles
    51103        define ( 'DSL_CSS_FILE', 'dsl.css' );
    52     // value that is shown on and returned by the form's submit button
    53     define ( 'DSL_SUBMIT_BUTTON_VALUE', 'Send it!' );
    54     // validation requirement for maximum number of characters in the comments
    55     define ( 'DSL_MAX_COMMENT_CHARS', 250);
    56     // validation requirement for minimum number of characters in the name
    57     define ( 'DSL_MIN_NAME_CHARS', 1);
    58     // validation requirement for maximum number of characters in the name
    59     define ( 'DSL_MAX_NAME_CHARS', 30);
    60    
    61 /********************************************************
    62  * dslMakeCaptcha()
    63  *
    64  * Returns a Captchas.Net object
    65  * Uses the web service at http://captchas.net
    66  * See Captcha comparisons at http://davebezaire.com/tst/captcha/captcha.html
    67  *
    68  * All Captchas.Net confirmation parameters are in this routine.
    69  */
    70 function dslMakeCaptcha(){
    71     // load the library that was copied from http://captchas.net
    72     require_once(DSL_PLUG_PATH .'CaptchasDotNet.php');
    73     // ID provided in registration
    74     $id = 'dcapltchba';
    75     // private key provided in registration
    76     $key = 'rConlz8OdN77va2jsYyOeW3VOJCACT4qgCJzZeoh';
    77     // storage location for strings??? not really sure how this works
    78     $store = '/tmp/captchasnet-random-strings';
    79     // maximum time (seconds) that a captcha can be valid
    80     $time = '3600';
    81     // characters to use in the image
    82     $alphabet = 'abcdghkmnpqrstvwyz345';
    83     // number of characters in the image
    84     $count = '4';
    85     // size in pixels
    86     $height = '80';
    87     $width = '140';
    88     // RGB hex value
    89     $color = 'A0590A';
    90     // create and return the object
    91     return new CaptchasDotNet ($id, $key, $store, $time, $alphabet, $count, $width, $height, $color);
    92 }
    93 /********************************************************
    94  * Globals and constants that MUST NOT be customized
    95  */
    96     //Global data array used throughout
    97     $dslData = array();
    98     // calculate location of other templates file
    99     define ( 'DSL_OTHER_TEMPLATES_CALCULATED' , dslCustomized( 'PATH', DSL_OTHER_TEMPLATES ) );
    100 /********************************************************
    101  * dslShortcode()
    102  *
    103  * Adds shortcode "dsl-link" which produces the html tags to display a
    104  * link to the email request form.
     104    // filename for the Javascript; we only look for it in the plugin folder
     105    define ( 'DSL_JAVASCRIPT_FILE', 'dsl.js' );
     106    // Define our table name in the WordPress mySQL database
     107    // using the site's table prefix that we get from the $wpdb global.
     108    global $wpdb;
     109    define ( 'DSL_LOG_TABLE' , $wpdb->prefix . 'dslLog' );
     110    // Set plugin-database version
     111    define ( 'DSL_DB_VERSION', 'dbv1' );
     112    // Define the name used to store the db version in the database
     113    define ( 'DSL_NAME_OF_DB_VERSION_SETTING', 'dsl_db_version');
     114    // Set number of seconds to retain log records in database
     115    // e.g., 60 * 60 & 24 is 24 hours
     116    define ( 'DSL_LOG_KEEP_SECONDS', 60 * 60 * 24 );
     117/********************************************************
     118 * dslLink()
     119 *
     120 * Adds shortcode "dsl-link" and function dslLink()
     121 * which produce the html tags to display a
     122 * link to the email request form.
     123 *
     124 * The shortcode is used within the contents of a page or post.
     125 * The function is used in templates.
    105126 *
    106  * This link sends the ID of the post or page in which it is clicked.
     127 * The resulting link sends the ID of the post or page in which it is clicked.
    107128 * It also sends the URL of the page (which could be either a single post or a
    108129 * page of many posts) for use in a back link.
     
    113134 * e.g., [dsl-link text="Link it!" iconfile="myIcon.gif" show="both"].
    114135 */
    115 add_shortcode("dsl-link", "dslShortcode");
    116 function dslShortcode($atts){
     136add_shortcode("dsl-link", "dslLink");
     137function dslLink( $atts = array() ){
    117138    // Establish parameter names and defaults.
    118139    // These are the values that can be provided within the shortcode brackets,
    119140    // and the default values used if they are not specified.
    120141    $defaults = array(
    121                         'show' => 'text',                       // one of 'icon', 'text', 'both', 'debug'
    122                         'icon_file' => "email.gif",             // filename, including path if not same place as this script
    123                         'text' => ' Send a link to this article'    // link text
     142                        'show' => 'both',                       // one of 'icon', 'text', 'both'
     143                        'icon_file' => DSL_DEFAULT_ICON_FILE,   // filename, including path if not same place as this script
     144                        'text' => DSL_DEFAULT_LINK_TEXT         // link text
    124145                    );
    125146    // Merge the defaults with the inputs from the shortcode
     
    137158    // Next we add the current page URL to the query
    138159    $query .= '&dsl_back=' . "http://" . $_SERVER['HTTP_HOST']  . $_SERVER['REQUEST_URI'];
    139     // and build the anchor tag
    140     $anchor = '<a href="' . $link_page . $query . '" title="' . $text . '">';
    141     // Now we build the image tag
     160    // Use WordPress esc_attr to be sure there are no invalid or dangerous characters in text
     161    $text = esc_attr($text);
     162    // Build the anchor tag
     163    // Use the WordPress esc_url functions for extra safety against sending out invalid or dangerous characters
     164    $anchor = '<a href="' . esc_url($link_page . $query) . '" title="' . $text . '">';
     165    // Build the image tag
     166    // Check that specified icon file exists by calling PHP function getimagesize()
     167    // which returns an array with the size already formatted for an html tag at index 3
     168    // or FALSE if file does not exist.
     169    // If file doesn't exist, fall back to default.
     170    if ( false === ( $imgsiz = getimagesize(DSL_PLUG_PATH . $icon_file) ) ) {
     171        $icon_file = $defaults['icon_file'];
     172        $imgsiz = getimagesize(DSL_PLUG_PATH . $icon_file);
     173    }
    142174    $imgurl = DSL_PLUG_URL . $icon_file;
    143     // Next we use the PHP getimagesize function, which returns an array with the
    144     // size already formatted for an html tag at index 3,
    145     $imgsiz = getimagesize(DSL_PLUG_PATH . $icon_file);
    146     // Here we put together the entire img tag
    147     $img = '<img src="' . $imgurl . '" ' . $imgsiz[3] . ' />';
     175    // Put together the entire img tag
     176    // Escape the url for safety, but trust the output from getimagesize to be valid
     177    $img = '<img src="' . esc_url($imgurl) . '" ' . $imgsiz[3] . ' />';
    148178    // Now we use the value of the $show parameter to determine what to output
    149179    switch ($show) {
    150         case 'debug':
    151             $output = "from my code";
    152             $output .= ", the post id is " . $post_id;
    153             $link_icon_text = $anchor . $img . $text . '</a>';
    154             $link_icon_only = $anchor . $img . '</a>';
    155             $link_text_only = $anchor . $text . '</a>';
    156             $output .= " Here are the links: " . $link_icon_only . " and " . $link_icon_text. " and " . $link_text_only;
    157             break;
     180// special option for debug
     181// comment this out for production
     182// case 'debug':
     183// $output = "from my code";
     184// $output .= ", the post id is " . $post_id;
     185// $link_icon_text = $anchor . $img . $text . '</a>';
     186// $link_icon_only = $anchor . $img . '</a>';
     187// $link_text_only = $anchor . $text . '</a>';
     188// $output .= " Here are the links: " . $link_icon_only . " and " . $link_icon_text. " and " . $link_text_only;
     189// break;
    158190        case 'both':
    159191            $output = $anchor . $img . $text . '</a>';
     
    166198            break;
    167199        default:
    168             $output = "Invalid value for show";
     200            $output = DSL_INVALID_SHOW_VALUE;
    169201    }
    170202    return $output;
     
    173205 * dslMain()
    174206 *
    175  * This is the entry point for all requests for a dlb's Send-A-Link.
     207 * This is the entry point for all requests to dlb's Send-A-Link.
    176208 *
    177209 * WordPress calls this routine every time it outputs any page or post,
     
    179211 * WordPress also calls this routine whenever it receives an AJAX request that
    180212 * specifies "action" equal to "dsl-form" because of the calls to add_action()
    181  * with  'wp_ajax_nopriv' and 'wp_ajax'.
     213 * with  'wp_ajax_nopriv' (called when a registered user is logged in) and
     214 * 'wp_ajax' (called for an unregistered visitor).
    182215 *
    183216 * This routine checks for existance of parameters in the URL query and POST variables
    184  * that indicate a dsl page request and handles the page. It quickly determine if
     217 * that indicate a dsl page request and handles the request. It quickly determines if
    185218 * the requested page needs this routine or not, and simply returns if not needed.
    186219 *
     
    188221 * message, with the confirmation message initially hidden. When the AJAX request sends
    189222 * the user input, this routine validates it sends an AJAX response which can
    190  * either be error message(s) or a confirmation message.
     223 * either be error message(s) or a confirmation message. If the user does not have
     224 * Javascript turned on, then the response is sent by the browser as a regular POST
     225 * instead of an AJAX, so this routine will send its response as a new page.
    191226 *
    192227 * When there are no errors, this routine actually sends the email and records it in a log.
     
    194229 * The trigger for this routine to take over is existance of query parameter 'dsl_pid'
    195230 * which must contain the ID of a page or post, e.g., http://Bezaires.com?dsl_pid=1234
    196  *
    197231 */
    198232add_action( 'template_redirect', 'dslMain' );
     
    201235function dslMain() {
    202236    global $dslData;
    203     // load the inputs into $dslData
     237    // load the inputs into the $dslData array
    204238    dslGetInputs();
    205239    // quickly return if we do not handle this type of request
     240    // return if we don't have a URL query parameter of 'dsl_pid'
    206241    if( ! isset($dslData['dsl_pid']) ) return;
     242    // insure received value is an integer,
     243    $dslData['dsl_pid'] = filter_var($dslData['dsl_pid'], FILTER_SANITIZE_NUMBER_INT);
     244    // get the post using it as a post id number
    207245    $dslData['post'] = get_post($dslData['dsl_pid']);
    208     // must be an accessible post or a page
     246    // return if we did not find a post or a page
    209247    if (NULL == $dslData['post'] || ('post' != $dslData['post']->post_type && 'page' != $dslData['post']->post_type) ) return;
    210     // Value of the form's submit button tells us
    211     // whether to show the page or to process the inputs
    212     switch ($dslData['dsl_submit']) {
    213         case "":
     248    // Now we know that request is of the type we handle
     249    // Check value of back link
     250    // Replace it with one pointing to the post if:
     251    // - it was not provided at all
     252    // - it contains invalid URL characters
     253    // - it does not contain the URL of this blog
     254    if (   
     255            ( ! isset( $dslData['dsl_back'] ) )
     256        ||  filter_var( $dslData['dsl_back'], FILTER_SANITIZE_URL ) != ( $dslData['dsl_back'] )
     257        ||  ( false === strpos( $dslData['dsl_back'], home_url() ) )
     258        ) {
     259        // Replace by setting it to a URL that points to the post/page
     260        $dslData['dsl_back'] = get_permalink( $dslData['post'] );
     261    }
     262    // setup array to accumulate the responses we'll send back
     263    $msg = array();
     264    // Check for security or structural errors that may impede later processing including
     265    // the submit button, the WordPress nonce, and the IP address.
     266    // If nonce fails, consider the entire request a suspect security risk.
     267    // If an unknown submit button value, not sure what we have, so don't trust it.
     268    // Not sure how we get an invalid IP address, but it's a bad thing if we do.
     269    // For all of these cases, send a general error message and reset the form fields.
     270    if (    ( $dslData['dsl_submit'] != '' && $dslData['dsl_submit'] != DSL_SUBMIT_BUTTON_VALUE )
     271         || ( $dslData['dsl_submit'] != '' && ( ! wp_verify_nonce( $dslData['dsl_nonce'], 'dsl-form' ) ) )
     272         || ( false === $dslData['ip'] )
     273        )  {
     274        // flag to force values to be reset
     275        $msg['reset'] = true;
     276        // the $errors array generally contains error messsages with the array key
     277        // equal to the name of the form field that contains the error. to handle
     278        // this form-level error, we put the message into the hidden post id field,
     279        // which is at the top of the form, so that is where it will be displayed.
     280        $errors['dsl_pid'] = DSL_GENERAL_ERROR_MESSAGE;
     281    }
     282    // If no errors yet, we know we have a valid request.
     283    // The driving parameters have been tested,
     284    // and inputs have been recorded in the $dslData array.
     285    // Use the submit button value to determine
     286    // whether to show the form or to process the inputs.
     287    if (    ( count($errors) == 0 )
     288        &&  ( $dslData['dsl_submit'] == '')
     289        ) {
    214290            // there was no submit button value,
    215             // so show a page with the form and confirmation sections.
    216             // First tell WordPress to run the function that adds the scripts and styles to the page
    217             add_action('wp_enqueue_scripts', 'dslEnq');
    218             // read template from file
    219             $input = file_get_contents( dslCustomized( 'PATH', DSL_PAGE_TEMPLATE ) );
    220             // perform substitutions for placeholders
    221             dslSubstitutePlaceholders(&$input);
    222             // send page headers
    223             get_header();
    224             // send template
    225             echo $input;
    226             // send footers and sidebar
    227             get_sidebar();
    228             get_footer();
    229             break;
    230         case DSL_SUBMIT_BUTTON_VALUE:
    231             // submit button value says we got a response from the user
    232             // check inputs for errors
    233             $errors = dslValidateInputs();
    234             // build array of responses to send via AJAX
    235             $msg = array();
    236             if (isset($errors['dsl_nonce'])) {
    237                 // major security breach was found
    238                 // send back a reset
    239                 // to cause Javascript to report an internal error and generate a reload
    240                 $msg['reset'] = true;
    241             } elseif (count($errors) > 0) {
    242                 // errors were found, so add the
    243                 // error messages to the response array
    244                 $msg['errors'] = $errors;
    245                 if (! isset($errors['dsl_captcha']) ) {
    246                     // No error with CAPTCHA, so we believe we are dealing with a person.
    247                     // Create a new nonce to use as a stand-in
    248                     // so that user doesn't have to do CAPTCHA again when correcting other errors
    249                     // and add it to the response array
    250                     $msg['secure'] =  wp_create_nonce( 'dsl-form-noCaptcha' );
    251                 }
    252             } else {
    253                 // no errors were found, so ready to send email
    254                 // format mail fields
    255                 dslPrepareMail();
    256                 // send the email
    257                 $dslData['mail-result'] = mail( $dslData['mail-to'],
    258                                                 $dslData['mail-subject'],
    259                                                 $dslData['mail-message'],
    260                                                 $dslData['mail-headers']);
    261                 // add the confirms object to the response array
    262                 $msg['confirms'] = array(
    263                                             'status'    => $dslData['mail-result'],
    264                                             'body'      => dslFormatConfirm()
    265                                         );
    266             }
    267             // add the received values to the response array for debugging
    268             // comment this out for production
    269             $msg['values'] = $dslData;
    270             // convert all of the responses in the array into a JSON string
    271             $msg = json_encode($msg);
    272             // send header and response string
    273             header( "Content-Type: application/json" );
    274             echo $msg;
    275             break;
    276         default:
    277             echo 'A general processing error occurred (i.e., received a dsl_submit value of |' . $dslData['dsl_submit'] . '|<br />' .
    278                 'We apologize for the inconvenience and appreciate your patience. Please try again.';
    279             break;
     291            // so show the blank form and then exit
     292            dslShowPage('blank');
     293            exit;
     294    }
     295    // If no errors yet, we know we have received inputs from the user
     296    if (count($errors) == 0) {
     297        // Insure the proper version of the database table is installed
     298        dslSetupDatabase();
     299        // get log record for this IP address
     300        $dslData['log'] = dslReadLog( $dslData['ip'] );
     301        // check inputs for errors
     302        $errors = dslValidateInputs();
     303    }
     304    // If no errors yet, try to send the mail
     305    // If fails, load an error message to the $errors array
     306    // If succeeds, load confirm to the $msg array
     307    // and record in the log
     308    if (count($errors) == 0) {
     309        dslSendMail();
     310        if ( TRUE !== $dslData['mail-result'] ) {
     311            // the send failed for no known reason,
     312            // so load a form-level error message
     313            $errors['dsl_pid'] = DSL_SENDING_ERROR_MESSAGE;
     314        } else {
     315            // add the confirms object to the response array
     316            $msg['confirms'] = array(
     317                                        'status'    => $dslData['mail-result'],
     318                                        'body'      => dslFormatConfirm()
     319                                    );
     320            // update log for IP with current time
     321            dslUpdateLog( $dslData['ip'], time() );
     322            // clear old records out of the log
     323            // (this should really be a CRON job or a mySQL trigger someday)
     324            dslFlushLog();
     325        }
     326    }
     327    // At this point, we either have message(s) in the $error array,
     328    // or we have successfully sent the email and noted it in the $msg array
     329    // If there are errors, put them into $msg array
     330    // and turn off CAPTCHA
     331    if (count($errors) > 0) {
     332        $msg['errors'] = $errors;
     333        if ( (! $msg['reset']) && (! isset($errors['dsl_captcha'])) ) {
     334            // No reset errors and no CAPTCHA error,
     335            // so we believe we are dealing with a person.
     336            // Create a new nonce to use as a stand-in
     337            // so that user doesn't have to do CAPTCHA again when correcting other errors
     338            // and add it to the response array
     339            $msg['secure'] =  wp_create_nonce( 'dsl-form-noCaptcha' );
     340        }
     341    }
     342    // At this point we have the $msg array containing all our responses
     343// add the received values to the response array for debug
     344// comment this out for production
     345//$msg['values'] = $dslData;
     346    // decide whether user has Javascript enabled and respond accordingly
     347    if ( isset($dslData['action']) && $dslData['action'] == 'dsl-form' ) {
     348        // we received the response via AJAX, so send it back that way
     349        // convert all of the responses in the array into a JSON string
     350        $msg = json_encode($msg);
     351        // send header and response string
     352        header( "Content-Type: application/json" );
     353        echo $msg;
     354    } else {
     355        // user doesn't have Javascript available, so resend entire page
     356        dslShowPage('with-feedback', $msg);
    280357    }
    281358    exit();
     
    284361 * dslGetInputs()
    285362 *
    286  * Pulls all of the inputs received via URL query or POST values into an array
     363 * Pulls all of the inputs received via URL query or POST into an array
    287364 */
    288365function dslGetInputs(){
     
    297374    $tmp = $_REQUEST;
    298375    foreach ($tmp as $key => $value) {
    299         if (substr($key, 0, 3) == 'dsl') {
     376        if (substr($key, 0, 3) == 'dsl' || $key == 'action') {
    300377            // All of our variables begin with "dsl", so we find and store them.
     378            // We also need the "action" that WordPress uses with AJAX
    301379            // It seems that stripslashes() is needed because WordPress apparently does an addslashes()
    302380            // which puts a backslash before every apostrophe. This is similar to a vestigal
     
    306384        }
    307385    }
     386    // add the user's IP address
     387    $dslData['ip'] = dslGetIP();
     388}
     389/********************************************************
     390 * dslGetIP()
     391 *
     392 * Gets ip address from any of multiple locations,
     393 * or returns FALSE if none can be found.
     394 * Verifies against invalid IP formats or characters with
     395 * PHP function filter_var()
     396 *
     397 * copied from http://www.stevekamerman.com/2006/06/storing-ip-addresses-in-mysql-with-php/
     398 */
     399function dslGetIP() {
     400    if (filter_var($_SERVER["HTTP_CLIENT_IP"], FILTER_VALIDATE_IP)) {
     401        return $_SERVER["HTTP_CLIENT_IP"];
     402    }
     403    foreach (explode(",",$_SERVER["HTTP_X_FORWARDED_FOR"]) as $ip) {
     404        if (filter_var(trim($ip), FILTER_VALIDATE_IP)) {
     405            return $ip;
     406        }
     407    }
     408    if (filter_var($_SERVER["HTTP_PC_REMOTE_ADDR"], FILTER_VALIDATE_IP)) {
     409        return $_SERVER["HTTP_PC_REMOTE_ADDR"];
     410    } elseif (filter_var($_SERVER["HTTP_X_FORWARDED"], FILTER_VALIDATE_IP)) {
     411        return $_SERVER["HTTP_X_FORWARDED"];
     412    } elseif (filter_var($_SERVER["HTTP_FORWARDED_FOR"], FILTER_VALIDATE_IP)) {
     413        return $_SERVER["HTTP_FORWARDED_FOR"];
     414    } elseif (filter_var($_SERVER["HTTP_FORWARDED"], FILTER_VALIDATE_IP)) {
     415        return $_SERVER["HTTP_FORWARDED"];
     416    } elseif (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP)) {
     417        return $_SERVER["REMOTE_ADDR"];
     418    } else {
     419        return false;
     420    }
    308421}
    309422/********************************************************
    310423 * dslValidateInputs()
    311424 *
    312  *  Given an array of field values which has indices equal to the field names,
     425 *  Given the global array of field values which has indices equal to the field names,
    313426 *  check for valid inputs.
    314427 *  Returns an array of error messages with indices equal to the field names.
     
    316429function dslValidateInputs(){
    317430    global $dslData;
    318     $errors = array();
    319    
    320     // Check the WordPress nonce
    321     // If this fails, consider the entire request suspect
    322     if ( ! wp_verify_nonce( $dslData['dsl_nonce'], 'dsl-form' ) ) {
    323         $errors['dsl_nonce'] = 'was invalid';
    324     }
    325    
     431       
    326432    // Check security field (anti-bot)
    327     if (isset($dslData['dsl_secure'])) {
     433    if ( isset($dslData['dsl_secure']) && $dslData['dsl_secure'] != '' ) {
    328434        // check the second nonce that was sent after a captcha was previously satisfied
    329435        if (false === wp_verify_nonce( $dslData['dsl_secure'], 'dsl-form-noCaptcha' )) {
    330             $errors[dsl_secure] = 'Security check failed';
     436            $errors[dsl_secure] = DSL_INVALID_NONCE;
    331437        }
    332438    } else {
     
    334440        $captcha = dslMakeCaptcha();
    335441        if ( ! $captcha->validate($dslData['dsl_captcha_random']) ) {
    336             $errors['dsl_captcha'] = 'Internal captcha error';
     442            $errors['dsl_captcha'] = DSL_INTERNAL_CAPTCHA_ERROR;
    337443        } elseif ( ! $captcha->verify($dslData['dsl_captcha']) ) {
    338             $errors['dsl_captcha'] = 'Must match characters in image';
     444            $errors['dsl_captcha'] = DSL_INCORRECT_CAPTCHA_ERROR;
    339445        }
     446    }
     447   
     448    // Check for sufficient time since last send by this IP address
     449    if ( (time() - $dslData['log']->time) < DSL_MIN_SEND_INTERVAL ) {
     450        $errors['dsl_submit'] = sprintf(DSL_MIN_SEND_INTERVAL_MESSAGE, DSL_MIN_SEND_INTERVAL );
    340451    }
    341452   
     
    348459    dslCheckName('dsl_rname', $errors); // recipient's
    349460   
    350     // Check for valid comments
    351     $invalidCommentChars = "/[<>]/";
     461    // Check for valid comments using rules that WordPress applies to comments
    352462    if (strlen(trim($dslData['dsl_comments'])) > DSL_MAX_COMMENT_CHARS ) {
    353         $errors['dsl_comments'] = 'Must be no more than ' . DSL_MAX_COMMENT_CHARS . ' characters';
    354     } elseif (1 === preg_match($invalidCommentChars, $dslData['dsl_comments'])) {
    355         $errors['dsl_comments'] = 'Must not contain < or >';
     463        $errors['dsl_comments'] = sprintf(DSL_MAX_CHARS_ERROR, DSL_MAX_COMMENT_CHARS);
     464    } elseif ( $dslData['dsl_comments'] != wp_kses( $dslData['dsl_comments'], wp_kses_allowed_html( 'comment' ) ) ) {
     465        $errors['dsl_comments'] = DSL_INVALID_COMMENTS_CHARS;
    356466    }
    357467   
     
    369479    global $dslData;
    370480    if ( $dslData[$fieldName] != filter_var($dslData[$fieldName], FILTER_SANITIZE_EMAIL)) {
    371         $errors[$fieldName] = 'May not contain invalid characters';
     481        $errors[$fieldName] = DSL_INVALID_EMAIL_CHARS;
    372482    } elseif ( ! filter_var($dslData[$fieldName], FILTER_VALIDATE_EMAIL)) {
    373         $errors[$fieldName] = 'Must be a valid email address';
     483        $errors[$fieldName] = DSL_INVALID_EMAIL_ERROR;
    374484    }
    375485}
     
    384494function dslCheckName($fieldName, &$errors){
    385495    global $dslData;
    386     $invalidNameChars = "/[^ a-zA-Z0-9._%-',]/"; // invalid chars are those NOT in the list
     496    $invalidNameChars = "/[^ a-zA-Z0-9._%-',\"]/"; // invalid chars are those NOT in the list
    387497    if (strlen(trim($dslData[$fieldName])) < DSL_MIN_NAME_CHARS) {
    388         $errors[$fieldName] = 'Must be at least ' . DSL_MIN_NAME_CHARS . ' character(s)';
     498        $errors[$fieldName] = sprintf( DSL_MIN_CHARS_ERROR, DSL_MIN_NAME_CHARS );
    389499    } elseif (strlen(trim($dslData[$fieldName])) > DSL_MAX_NAME_CHARS) {
    390         $errors[$fieldName] = 'Must be no more than ' . DSL_MAX_NAME_CHARS . ' characters';
     500        $errors[$fieldName] = sprintf( DSL_MAX_CHARS_ERROR, DSL_MAX_NAME_CHARS );
    391501    } elseif (1 === preg_match($invalidNameChars, $dslData[$fieldName])) {
    392         $errors[$fieldName] = 'Must contain only: A-Z, a-z, 0-9, . , _ - \' %';
    393     }
     502        $errors[$fieldName] = DSL_INVALID_NAME_CHARS;
     503    }
     504}
     505/********************************************************
     506 * dslShowPage()
     507 *
     508 * Shows page with form and hidden confirmation
     509 *
     510 * Normally shows the blank form and then AJAX/Javascript
     511 * handles responses and displaying feedback.
     512 * But, if no Javascript on client, this routine has to
     513 * show the page with feedback.
     514 *
     515 * $type parameter can be either:
     516 * - 'blank' to show the blank form
     517 * - 'with-feedback' to show the form with error or confirmation
     518 */
     519function dslShowPage($type, $msg = null){
     520    global $dslData;
     521    // First tell WordPress to run the function that adds the scripts and styles to the page
     522    add_action('wp_enqueue_scripts', 'dslEnq');
     523    // read template from file
     524    $input = file_get_contents( dslCustomized( 'PATH', DSL_PAGE_TEMPLATE ) );
     525    // perform substitutions for placeholders
     526    dslSubstitutePlaceholders($input);
     527    // if no Javascript, format the response into the template
     528    if ($type == 'with-feedback') { dslFormatFeedback($input, $msg); }
     529    // send page headers
     530    get_header();
     531    // send template
     532    echo $input;
     533    // send footers and sidebar
     534    get_sidebar();
     535    get_footer();
     536}
     537/********************************************************
     538 * dslFormatFeedback()
     539 *
     540 * When there is no Javascript on the client, we need to do everthing
     541 * here that dsl.js normally handles
     542 *
     543 * Accepts the array of responses that normally would have been sent via AJAX
     544 * and a reference to the blank template, and modifies the template string directly
     545 */
     546function dslFormatFeedback(&$blank, $msg){
     547    global $dslData;
     548    if ( $msg['reset'] ) {
     549        // perform reset, showing the form-level error
     550        dslFormatReset($blank, $msg['errors']['dsl_pid']);
     551    } elseif ( $msg['errors'] != null ) {
     552        // redisplay the form with error messages
     553        // put error messages into label spans
     554        foreach ($msg['errors'] as $field => $txt) {
     555            $pattern = '#(<label.*?for="' . $field . '".*?>.*?<span.*?class="dsl-error-field".*?>).*?</span>#s';
     556            $replacement = '$1<br />' . $txt . '</span>';
     557            $blank = preg_replace($pattern, $replacement, $blank);
     558        }
     559        // put values into inputs
     560        $fieldsToDo = array('dsl_rname', 'dsl_raddress', 'dsl_sname', 'dsl_saddress');
     561        foreach ($fieldsToDo as $fld){
     562            $pattern = '#<input([^>]*?name="' . $fld . '")#s';
     563            $replacement = '<input value="' . htmlentities($msg['values'][$fld]) . '" $1';
     564            $blank = preg_replace($pattern, $replacement, $blank);
     565        }
     566        // put value into textarea
     567        $pattern = '#(<textarea[^>]*?name="dsl_comments"[^>]*?>)(</textarea>)#';
     568        $replacement = '$1' . htmlentities($msg['values']['dsl_comments']) . '$2';
     569        $blank = preg_replace($pattern, $replacement, $blank);
     570        // turn off CAPTCHA is already satisfied
     571        if ( $msg['secure'] != null ) {
     572            // put value of secure into input value
     573            $pattern = '#(<input[^>]*?name="dsl_secure"[^>]*?value=)"("[^>]*?>)#';
     574            $replacement = '$1"' . $msg['secure'] . '$2';
     575            $blank = preg_replace($pattern, $replacement, $blank);
     576            // hide captcha div
     577            $pattern = '#(<div[^>]*?id="dsl-captcha")([^>]*?>)#';
     578            $replacement = '$1 style="display:none;" $2';
     579            $blank = preg_replace($pattern, $replacement, $blank);
     580        }
     581    } elseif ( $msg['confirms'] != null ) {
     582        // send the confirm page
     583        // use display:none to hide the form
     584        $pattern = '#(<div[^>]*?id="dsl-form-div")([^>]*?>)#';
     585        $replacement = '$1 style="display:none;" $2';
     586        $blank = preg_replace($pattern, $replacement, $blank);
     587        // use display:block to show the confirm
     588        $pattern = '#(<div[^>]*?id="dsl-confirm-div")([^>]*?>)#';
     589        $replacement = '$1 style="display:block;" $2';
     590        $blank = preg_replace($pattern, $replacement, $blank);
     591        // put the confirm text into the div
     592        $pattern = '#(<div[^>]*?id="dsl-confirm-div"[^>]*?>.*?<div [^>]*?>)#s';
     593        $replacement = '$1' . $msg['confirms']['body'];
     594        $blank = preg_replace($pattern, $replacement, $blank);
     595    } else {
     596        // general error reset
     597        dslFormatReset( $blank, DSL_GENERAL_ERROR_MESSAGE );
     598    }
     599}
     600/********************************************************
     601 * dslFormatReset()
     602 *
     603 * do reset by not filling in values
     604 * and not showing errors other than
     605 * the general processing error
     606 */
     607function dslFormatReset(&$blank, $txt) {
     608    // do reset by not filling in values
     609    // and not showing errors other than
     610    // the general processing error
     611    $field = 'dsl_pid';
     612    $pattern = '#(<label.*?for="' . $field . '".*?>.*?<span.*?class="dsl-error-field".*?>).*?</span>#s';
     613    $replacement = '$1<br />' . $txt . '</span>';
     614    $blank = preg_replace($pattern, $replacement, $blank);
    394615}
    395616/********************************************************
     
    406627    global $dslData;
    407628    // Each enqueue call causes WordPress to put the associated link or script
    408     // tags into the <head> section of the page
    409     // Always enqueue the base dsl styles from the plugin folder
     629    // tags into the <head> section of the page. The first parameter is a handle so
     630    // that task can be referred to later.
     631    // First enqueue the base dsl styles file from the plugin folder.
    410632    wp_enqueue_style( 'dsl-css', DSL_PLUG_URL . DSL_CSS_FILE );
    411     // Enqueue the style overrides from the theme folder
     633    // Enqueue the style overrides from the theme folder.
     634    // The 3rd parameter is the handle from the previous call, and thus insures
     635    // that this one comes later.
    412636    if ( file_exists(DSL_THEME_PATH . DSL_CSS_FILE) ) {
    413637        wp_enqueue_style( 'dsl-css-from-theme', DSL_THEME_URL . DSL_CSS_FILE, array('dsl-css') );
    414638    }
    415     wp_enqueue_script( 'dsl-js', DSL_PLUG_URL . DSL_JAVASCRIPT_FILE ); // js file
    416     // The localize call puts a script into the <HEAD> section of the page
    417     // that creates an object (ajaxData) containing an array of key:value pairs.
    418     // Thus, in javascript ajaxData is a global variable that contains some
    419     // values that the script needs in order to send a response via AJAX.
    420     // Read templates from file to get the error-alert
    421     $input = file_get_contents(DSL_OTHER_TEMPLATES_CALCULATED);
    422     $input = dslGetSection($input, 'error-alert');
    423     dslSubstitutePlaceholders(&$input);
     639    // Enqueue the Javascript file
     640    wp_enqueue_script( 'dsl-js', DSL_PLUG_URL . DSL_JAVASCRIPT_FILE );
     641    // The localize call will put a script tag into the <HEAD> section of the page
     642    // to create a Javascript object (ajaxData in this case) containing an array of key:value pairs.
     643    // Thus, in the Javascript, ajaxData becomes a global variable that we use to send some data
     644    // from the server.
    424645    wp_localize_script( 'dsl-js', 'ajaxData',
    425646        array(
     
    427648            'url' =>  admin_url( 'admin-ajax.php' ),
    428649            // Javascript includes this 'action' to tell WordPress which routine
    429             // will handle the response as defined in the add_action('wp-ajax_')
     650            // will handle the AJAX response as defined in the add_action('wp-ajax_') call
    430651            'action' => 'dsl-form',
    431652            // this is text for the error alert
    432             'errorAlert' => $input
     653            'errorAlert' => sanitize_text_field( DSL_ERROR_ALERT ),
     654            // this is text for an unexplained error
     655            'errorGeneral' => sanitize_text_field( DSL_GENERAL_ERROR_MESSAGE )
    433656        )
    434657    );
     
    437660 * dslCustomized()
    438661 *
    439  * Given a path/filename relative to the theme or plugin folder,
    440  * Returns a reference to it in the theme folder if the file exists there,
    441  * or to it in the plugin folder if file exists there, or
     662 * Return URL or PATH to file in theme folder or plugin folder
     663 *
     664 * Given a filename with path relative to the plugin folder,
     665 * returns a reference to it in the theme folder if the file exists there,
     666 * or to it in the plugin folder if it exists there, or
    442667 * FALSE if it exists in neither place.
     668 *
    443669 * $type can be either URL or PATH
    444670 */
    445671function dslCustomized($type, $file){
     672    // check in theme folder
    446673    $tmp = DSL_THEME_PATH . $file;
    447674    if ( file_exists($tmp) ) {
     675        // found it, so return URL or PATH to it
    448676        $tmp = ($type == 'PATH') ?  $tmp : DSL_THEME_URL . $file;
    449677    } else {
     678        // check in plugin folder
    450679        $tmp = DSL_PLUG_PATH . $file;
    451680        if ( file_exists($tmp) ) {
     681            // found it, so return URL or PATH
    452682            $tmp = ($type == 'PATH') ?  $tmp : DSL_PLUG_URL . $file;
    453683        } else {
     684            // not found, so return false
    454685            $tmp = FALSE;
    455686        }
     
    458689}
    459690/********************************************************
    460  * dslPrepareMail()
     691 * dslMakeCaptcha()
     692 *
     693 * Returns a Captchas.Net object
     694 *
     695 * This routine reads in all of the CAPTCHA customization parameters
     696 * from a configuration file.
     697 * The $id and $key must be replaced with values received by registering
     698 * at http://captchas.net.
     699 *
     700 * Uses the web service at http://captchas.net
     701 * See Captcha comparisons at http://davebezaire.com/tst/captcha/captcha.html
     702 */
     703function dslMakeCaptcha(){
     704    // load the configuration file, hopefully from the theme folder,
     705    // with fallback to that in the plugin folder
     706    include ( dslCustomized( 'PATH', DSL_CAPTCHA_CONFIG ) );
     707    // load the library that was copied from http://captchas.net
     708    require_once(DSL_PLUG_PATH .'CaptchasDotNet.php');
     709    // create and return the object
     710    return new CaptchasDotNet ($id, $key, $store, $time, $alphabet, $count, $width, $height, $color);
     711}
     712/********************************************************
     713 * dslSendMail()
     714 *
     715 * Sends email message
    461716 *
    462717 * Reads the email message template from an external file,
    463718 * formats the various parts of the email message, and
    464  * returns them in the global array. The body of the message is
    465  * also returned separately so that it can be
    466  * displayed on the confirmation page.
    467  */
    468 function dslPrepareMail(){
    469     global $dslData;
    470     // read template from file
    471     $input = file_get_contents(DSL_OTHER_TEMPLATES_CALCULATED);
     719 * puts them in the global array. The body of the message is
     720 * also included separately so that it can be
     721 * displayed on the confirmation page. Then sends the email.
     722 *
     723 * Result of the mail send call is also put in the global array
     724 */
     725function dslSendMail(){
     726    global $dslData;
     727    // read templates from file
     728    $input = file_get_contents(dslCustomized( 'PATH', DSL_OTHER_TEMPLATES ));
     729    // get the message section from the templates
    472730    $input = dslGetSection( $input, 'message');
    473731    // perform substitutions for placeholders
     732    // this puts in the names, receiver's address, sender's comments, post title, etc.
    474733    dslSubstitutePlaceholders(&$input);
    475     // remove comments paragraphs if no comments
    476     if (trim($dslData['dsl_comments']) == '') {$input = preg_replace("#<p\s*?class=\"comment\".*?</p>#s", "", $input);}
     734    // if no comments, remove comments paragraphs
     735    if (trim($dslData['dsl_comments']) == '') {
     736        $input = preg_replace("#<p\s*?class=\"comment\".*?</p>#s", "", $input);
     737    }
    477738    // extract the body for display on confirmation page
    478739    preg_match("#<body>(.*)</body>#s", $input, $tmp);
     
    482743    $msgSubj = $tmp[1];
    483744    // subject is plain text, so convert emphasis tags to quotes, and remove htmlentities()
     745    // and sanitize as text
    484746    $msgSubj = str_replace('<em>', '"', str_replace('</em>', '"', $msgSubj));
    485747    $msgSubj = html_entity_decode($msgSubj);
     748    $msgSubj = sanitize_text_field($msgSubj);
    486749    // To send HTML mail, the Content-type headers must be set
    487     // Note that the charset is also mentioned in the <HEAD> above
     750    // Note that the charset is also mentioned in the <HEAD> in the template
    488751    $hdrs  = 'MIME-Version: 1.0' . "\r\n";
    489752    $hdrs .= 'Content-type: text/html; charset=utf-8' . "\r\n";
    490     $hdrs .= 'From:' . $dslData['dsl_sname'] . ' <' . $dslData['dsl_saddress'] . '>' . "\r\n";
     753    // add senders name and address in the headers
     754    $hdrs .= 'From:' . sanitize_text_field($dslData['dsl_sname']) . ' <' . $dslData['dsl_saddress'] . '>' . "\r\n";
    491755    // Store the results in the $dslData array
    492756    $dslData['mail-to'] = $dslData['dsl_raddress'];
     
    495759    $dslData['mail-message-body'] = $msgBody;
    496760    $dslData['mail-headers'] = $hdrs;
     761    // send the email
     762    $dslData['mail-result'] = mail( $dslData['mail-to'],
     763                                    $dslData['mail-subject'],
     764                                    $dslData['mail-message'],
     765                                    $dslData['mail-headers']);
    497766}
    498767/********************************************************
     
    504773    global $dslData;
    505774    // read template from file
    506     $input = file_get_contents(DSL_OTHER_TEMPLATES_CALCULATED);
    507     // select either error or confirm section
    508     $out .= dslGetSection( $input, ($dslData['mail-result'] !== true) ? 'error' : 'confirm');
    509     // add on the navigation section
    510     $out .= dslGetSection($input, 'nav');
     775    $input = file_get_contents(dslCustomized( 'PATH', DSL_OTHER_TEMPLATES ));
     776    // get the confirm section
     777    $out .= dslGetSection( $input, 'confirm');
    511778    // perform the substitutions
    512     dslSubstitutePlaceholders(&$out);
     779    dslSubstitutePlaceholders($out);
    513780    return $out;
    514781}
     
    516783 * dslGetSection()
    517784 *
    518  * Returns the contents of a section of the template file
     785 * Returns the contents of a section from the template file
    519786 */
    520787function dslGetSection($str, $tag) {
     
    522789    // the leading and trailing #'s are required delimiters
    523790    // the trailing 's' after the last # says that dot should match newlines
    524     // the \s* followed by \S consumes white space, including newlines, between the end and start
    525     // of the tag brackets and included content
     791    // the \s* followed by \S consumes white space, including newlines,
     792    // between the end and start of the tag brackets and included content
    526793    $pattern = '#<' . $tag . '>\s*(\S.*\S)\s*</' . $tag . '>#s';
    527794    preg_match($pattern, $str, $match); // stores the first capture group in $match[1]
     
    547814    // Substitute current values for the placeholders
    548815    $template = str_replace("#dsl-post-title#",         htmlentities($dslData['post']->post_title),     $template);
    549     $template = str_replace("#dsl-recipient-name#",     $dslData['dsl_rname'],                          $template);
     816    $template = str_replace("#dsl-recipient-name#",     sanitize_text_field($dslData['dsl_rname']),     $template);
    550817    $template = str_replace("#dsl-recipient-address#",  $dslData['dsl_raddress'],                       $template);
    551     $template = str_replace("#dsl-sender-name#",        $dslData['dsl_sname'],                          $template);
     818    $template = str_replace("#dsl-sender-name#",        sanitize_text_field($dslData['dsl_sname']),     $template);
    552819    $template = str_replace("#dsl-post-id#",            $dslData['dsl_pid'],                            $template);
    553820    $template = str_replace("#dsl-post-author#",        $author,                                        $template);
    554821    $template = str_replace("#dsl-post-date#",          $date,                                          $template);
    555     $template = str_replace("#dsl-back-link#",          $dslData['dsl_back'],                           $template);
     822    $template = str_replace("#dsl-back-link#",          esc_url($dslData['dsl_back']),                  $template);
    556823    $template = str_replace("#dsl-comments#",           nl2br($dslData['dsl_comments']),                $template);
    557     $template = str_replace("#dsl-message-subject#",    $dslData['mail-subject'],                       $template);
     824    $template = str_replace("#dsl-message-subject#",    sanitize_text_field($dslData['mail-subject']),  $template);
    558825    $template = str_replace("#dsl-message-body#",       $dslData['mail-message-body'],                  $template);
    559     $template = str_replace("#dsl-submit-button#",      DSL_SUBMIT_BUTTON_VALUE,                            $template);
     826    $template = str_replace("#dsl-submit-button#",      DSL_SUBMIT_BUTTON_VALUE,                        $template);
    560827    $template = str_replace("#wp-nonce#",               wp_create_nonce('dsl-form'),                    $template);
    561828    $template = str_replace("#captcha-random#",         $captcha->random (),                            $template);
    562829    $template = str_replace("#captcha-image#",          $captcha->image(),                              $template);
    563830    $template = str_replace("#captcha-audio#",          $captcha->audio_url (),                         $template);
    564     $template = str_replace("#max-comment-chars#",      DSL_MAX_COMMENT_CHARS,                              $template);
    565     $template = str_replace("#min-name-chars#",         DSL_MIN_NAME_CHARS,                                 $template);
    566     $template = str_replace("#max-name-chars#",         DSL_MAX_NAME_CHARS,                                 $template);
     831    $template = str_replace("#max-comment-chars#",      DSL_MAX_COMMENT_CHARS,                          $template);
     832    $template = str_replace("#min-name-chars#",         DSL_MIN_NAME_CHARS,                             $template);
     833    $template = str_replace("#max-name-chars#",         DSL_MAX_NAME_CHARS,                             $template);
     834}
     835/********************************************************
     836 * dslSetupDatabase()
     837 *
     838 * Do one-time, initial setup tasks.
     839 *
     840 * WordPress calls this routine when the user presses the "Activate" link
     841 * in the Administrator's Plugins menu due to the register_activation_hook() call.
     842 * Since there is no hookable event when an upgraded version is installed,
     843 * we also call this routine on every run of the script after
     844 * we know we will be handling the page request.
     845 *
     846 * This routine sets up a database table to log
     847 * the IP address and time of each send.
     848 */
     849register_activation_hook( __FILE__, 'dslSetupDatabase' );
     850function dslSetupDatabase() {
     851    global $wpdb;
     852    // check if the currently installed database version
     853    // matches what will be installed by this routine,
     854    // and if so, simply return
     855    if ( get_option(DSL_NAME_OF_DB_VERSION_SETTING) == DSL_DB_VERSION ) return;
     856    // Define the command that will tell WordPress to create our table.
     857    // Note that this command is not strict SQL because the WordPress routines process it first.
     858    // This table will be used to remember sends for just a very short time, a day at the most.
     859    // It will store the user's IP address and the time of each send as follows:
     860    // - idnum is a unique key for each record added to the table
     861    // - ip is the address stored as a long integer by using PHP function ip2long()
     862    // - time is the time of the last send stored as a UNIX time stamp which is in seconds
     863    // - comment is for testing and learning about mySQL and myPhpAdmin
     864    // idnum is used as the unique, primary index
     865    // ip is indexed because it will be the search term on every query
     866    // time is indexed because it will be the sort term on every query
     867    $sqlCmd = "CREATE TABLE " . DSL_LOG_TABLE . " (
     868                                            idnum int NOT NULL AUTO_INCREMENT,
     869                                            ip int,
     870                                            time int,
     871                                            comment text,
     872                                            UNIQUE KEY  idnum (idnum),
     873                                            KEY ip (ip),
     874                                            KEY time (time)
     875                                            );";
     876    // Load the WordPress upgrade module which knows how to deal with structuring tables.
     877    // In particular, it handles a "create" query by analysing what is already in place
     878    // and changing it as necessary.
     879    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
     880    // the WordPress dbDelta function executes our command
     881    $e = dbDelta ( $sqlCmd );
     882    // Put a setting in the WordPress options table with the Plugin's database version number
     883    add_option( DSL_NAME_OF_DB_VERSION_SETTING, $wpdb->escape(DSL_DB_VERSION) );
     884}
     885/********************************************************
     886 * dslDeActivate()
     887 *
     888 * Deletes the plugin's database table and option.
     889 *
     890 * WordPress calls this routine when the user presses the "Deactivate" link
     891 * in the Administrator's Plugins menu.
     892 */
     893register_deactivation_hook( __FILE__, 'dslDeActivate' );
     894function dslDeActivate() {
     895    global $wpdb;
     896    // delete options created by plugin
     897    delete_option( DSL_NAME_OF_DB_VERSION_SETTING );
     898    // define command that will tell MySQL to delete the table created by plugin
     899    $sqlCmd = 'DROP TABLE IF EXISTS ' . DSL_LOG_TABLE .';';
     900    // send command thru the WordPress query object
     901    $e = $wpdb->query( $sqlCmd );
     902}
     903/********************************************************
     904 * dslUpdateLog
     905 *
     906 * Adds a log record for IP address with time of last send
     907 * and optional comment
     908 * See http://stackoverflow.com/questions/2754340/inet-aton-and-inet-ntoa-in-php
     909 * for info about storing an IP address in MySQL.
     910 */
     911function dslUpdateLog($ip, $time, $comment = '') {
     912    global $wpdb;
     913    // setup data to be stored
     914    $fields = array(
     915                    'ip'        => ip2long( $ip ), // IP address converted to long integer
     916                    'time'      => $time, // current time as UNIX timestamp
     917                    'comment'   => $wpdb->escape( $comment ) // optional comment
     918                    );
     919    // Tell WordPress to insert the record
     920    $wpdb->insert( DSL_LOG_TABLE, $fields );
     921}
     922/********************************************************
     923 * dslReadLog($ip)
     924 *
     925 * Returns a log object with:
     926 *  - the most recent log record for the given IP address, or
     927 *  - if no existing record for that IP, a dummy record with time equal to zero.
     928 */
     929function dslReadLog($ip) {
     930    global $wpdb;
     931    // Define sql query to get all records for this IP and sort them
     932    // by time descending
     933    $sqlCmd =   ' SELECT * FROM ' . DSL_LOG_TABLE .
     934                ' WHERE ip = ' . ip2long($ip) .
     935                ' ORDER BY time DESC'
     936                ;
     937    // Tell WordPress to execute the query and return the top row
     938    $logRow = $wpdb->get_row( $sqlCmd );
     939    // If row was found, convert IP back from integer to string
     940    // If not found, create a dummy row with time of zero
     941    if ( $logRow != null ) {
     942        $logRow->ip = long2ip($logRow->ip);
     943        return $logRow;
     944    } else {
     945        return (object)array('ip' => $ip, 'time' => 0, 'comment' => 'dummy');
     946    }
     947}
     948/********************************************************
     949 * dslFlushLog($ip)
     950 *
     951 * Deletes old records from the log
     952 *
     953 * Old are those earlier than the current time
     954 * less the DSL_LOG_KEEP_SECONDS.
     955 */
     956function dslFlushLog() {
     957    global $wpdb;
     958    // calculate the earliest time to be kept
     959    $deleteTime = time() - DSL_LOG_KEEP_SECONDS;
     960    // Define sql command to find earlier records and delete them
     961    $sqlCmd =   ' DELETE FROM ' . DSL_LOG_TABLE .
     962                ' WHERE time < ' . $deleteTime
     963                ;
     964    // Tell WordPress to execute the query
     965    $wpdb->query( $sqlCmd );
    567966}
    568967?>
  • dlbs-send-a-link/trunk/readme.txt

    r726782 r728099  
    55Requires at least: 3.5.1
    66Tested up to: 3.5.1
    7 Stable tag: 0.45
     7Stable tag: 0.95
    88License: GPLv2 or later
    99License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    1212
    1313== Description ==
    14 *dlb's Send-A-Link* allows visitors to send someone an email containing a link to the post or page. This is a preliminary, developmental release, and will be replaced by the first full release no later than June 30, 2013. Key features include:
     14*dlb's Send-A-Link* allows visitors to send someone an email containing a link to the post or page. This version, 0.95, is the final candidate for release, and will be superceded by version 1.0 after testing is complete, no later than June 25, 2013.
    1515
    16 - A shortcode can be put in your template(s) to show visitors an icon or text link to send an email.
     16Key features include:
     17
     18- Show visitors a link (icon and/or text) to send an email by adding a function call to your templates or a shortcode to your pages/posts.
    1719- The input form, confirmation page, and email message are all based on easily modifiable HTML templates.
     20- Spam protection includes CAPTCHA verification and limiting any given IP address to two messages per minute.
    1821- *Send-A-Link* loads fast because code is compact and does not use jQuery.
    19 - *Send-A-Link* uses Javascript/AJAX for fast, inobtrusive form handling.
    20 - Built and tested on WordPress v3.5.1; although it might work on earlier versions, it has not been test on them.
     22- *Send-A-Link* uses Javascript/AJAX for fast, inobtrusive form handling, but it degrades gracefully to provide full functionality to clients without Javascript.
     23- Built and tested on WordPress v3.5.1; although it might work on earlier versions, it has not been tested on them.
    2124
    22 The *Send-A-Link* code is heavily commented, especially regarding WordPress plugin interfaces and AJAX features. This makes it a suitable starting point for novice developers to explore their own programming interests. I have attempted to make the code very understandable, but I do not claim this to be *model* code because I am not an experienced developer myself. In fact, I would greatly appreciate constructive criticism.
    23    
    24 In the future, I will release new versions with the following features, in approximately this order:
     25*dlb's Send-A-Link* code is heavily commented, especially regarding WordPress plugin interfaces and AJAX features. This makes it a suitable starting point for novice developers to explore their own programming interests. I have attempted to make the code very understandable. However, I do not claim this to be *model* code because I am not an experienced developer myself. In fact, I would greatly appreciate constructive criticism.
     26
     27In the future, I plan to release new versions with the following features, in approximately this order:
    2528
    2629- Privacy reassurance that email addresses are not stored, but IP's are
    27 - Log mail sent with user's IP address to a file
    28 - Throttle sends to some number per time period from a given IP
    29 - Provide full functionality without Javascript
    3030- Localize to be ready for translation, including delivery of po and mo files and some instruction on how to use POEDIT to customize the wording, regardless of language. This could especially apply to the error messages.
     31- Attempt to use PHP's DOM handling routines for more robust and reliable formatting of responses when client does not have Javascript available
     32- Use a stored procedure to flush log daily to reduce number of times it is done
    3133- Rewrite using class construction
    32 - Add admin screens to modify things now defined as constants and templates
    33 - Move logging into database
     34- Add admin screens to specify some things now defined in constants and templates
    3435- Make it easy to turn CAPTCHA off or on
    3536- Make it easy to use other CAPTCHAs
     
    4142The manual method requires several steps: (1) Download the zip file to your computer. (2) Unzip the file. (3) Upload the `dlbs-send-a-link` folder to your `wp-content/plugins` directory. (4) Go to `Plugins > Installed Plugins` in your blog's Administration menu and click the `Activate` link under *dlb's Send-A-Link*.
    4243
    43 Either way, you simply insert the shortcode `[dsl-link]` in your posts and pages wherever you want to show a link or icon to your visitors offering the opportunity for them to send a message. In your templates, you can insert the code: `if( function_exists('dslShortcode') ) { echo dslShortcode( array('show'=>'both') ); }`
     44Customization of *dlb's Send-A-Link* is further described in the FAQ. Generally, it will be necessary to copy `dsl-page.html`, `dsl-templates.html`, and `dsl.css` from the plugin folder to your theme folder. Modify `dsl-page.html` to match the structure of your theme's `page.php` file. Modify `dsl-templates.html` to reflect the URL of your blog. Modify `dsl.css` to match the look & feel of your blog.
    4445
    45 Optionally, you can customize *dlb's Send-A-Link* as described in the FAQ.
     46You need to register at http://captchas.net (It's free!) to obtain your own `Username` and `Secret Key`. Copy file `dsl-captcha.php` from the plugin folder to the theme folder. Change the values of `$id` and `$key` from "demo" and "secret" to your own `Username` and `Secret Key` respectively.
     47
     48Finally, insert the shortcode `[dsl-link]` in your posts/pages wherever you want to show a link or icon to your visitors offering the opportunity to send a message. In your templates, insert the function `dslLink()`, typically as: `if( function_exists('dslLink') ) { echo dslLink(); }`. See FAQ to customize it.
     49
    4650
    4751== Frequently Asked Questions ==
     
    5761= Can I change the icon and text in the link? =
    5862
    59 Yes, by using these parameters in the shortcode or in the template code: `[dsl-link show="both" iconfile="Your-Icon.gif" text="Your Text" ]`. The value of `show` can be one of "icon", "text", or "both" to display only the icon, only the text or both. The value of `iconfile` must be the name of your file with a path relative to the plugin folder. The value of `text` will be the title which is typically shown as a tooltip, and will be displayed if `show` is either "text" or "both".
     63Yes. The parameters for shortcode `[dsl-link]` and function `dslLink()` are as follows: `show` can be "icon", "text", or "both" to display only the icon, only the text or both; `iconfile` is the name of your icon file, with a path relative to the plugin folder; `text` is shown as a tooltip and on the link if `show` is either "text" or "both". In the shortcode, enter them like this: `[dsl-link show="both" text="Your Text" iconfile="myIcon.gif" ]`. In the function, enter them like this: `dslLink( array( "show" => "both" , "text" => "Your Text" , "iconfile" => "myIcon.gif" ) )`.
     64
     65= How can I change to wording on the form's submit button? =
     66
     67This can only be changed in file `dsl.php`. Search for the definition of `DSL_SUBMIT_BUTTON_VALUE`. This change will need to be made again after an upgrade to a new version.
     68
     69= How can I change the maximum number of characters allowed in names and comments? =
     70
     71This can only be changed in file `dsl.php`. Search for the definitions of `DSL_MAX_COMMENT_CHARS`, `DSL_MAX_NAME_CHARS`, and `DSL_MIN_NAME_CHARS`. These changes will need to be made again after an upgrade to a new version.
     72
     73= How can I change the minimum time between sends? =
     74
     75This can only be changed in file `dsl.php`. Search for the definition of `DSL_MIN_SEND_INTERVAL`. This change will need to be made again after an upgrade to a new version.
    6076
    6177= How can I change the wording in the error messages? =
    6278
    63 In this version the error messages must be changed by editing file `dsl.php`, which is heavily commented to make it easier to navigate and alter the code. A future release will make this easier by using the WordPress localization system.
     79In this version of the plugin, the error messages must be changed by editing file `dsl.php`, which is heavily commented to make it easier to navigate and alter the code. A future release will make this easier by using the WordPress localization system. These changes will need to be made again after an upgrade to a new version.
    6480
    6581== Screenshots ==
     
    6985== Changelog ==
    7086
     87= 1.0 =
     88* Degrades gracefully to provide full functionality to users without Javascript
     89* Loads CAPTCHA configuration file from theme folder
     90
     91= 0.5 =
     92* Implements activate and deactivate routines to provide a database table for logging
     93* Logs IP address and time for every mail sent
     94* Flushes all log records more than one day old
     95* Limits send rate from an IP address to one message every 30 seconds
     96* Moved the last bit of visible text from Javascript file to template file
     97
    7198= 0.45 =
    72 * Added to the WordPress.org Plugin Directory
    73 * Improved the installation instructions in readme.txt
     99* Added *dlb's Send-A-Link* to the WordPress.org Plugin Directory
     100* Improved the formatting and installation instructions in readme.txt
    74101
    75102= 0.4 =
    76 Initial release:
    77 
    78 * Allows visitors to send someone an email containing a link to the post or page.
     103* Initial release
     104* Allows visitors to send someone an email containing a link to the post/page.
    79105* Visitor can optionally include comments in the message.
    80 * Provides a shortcode to put into templates to show an icon or text link to send an email.
     106* Provides a function  to put into templates and a shortcodeto to put into posts/pages to show an icon or text link to send an email.
    81107* The input form, confirmation page, and email message are based on HTML templates
    82 * Customized CSS styles and templates can be stored in the theme
     108* Customized CSS styles and templates can be stored in the theme folder
    83109* Uses Javascript/AJAX for fast, inobtrusive form handling.
    84110* Loads fast, based on compact code that does not use jQuery.
     
    88114== Upgrade Notice ==
    89115
     116= 0.95 =
     117This is the final candidate for release, and will be superceded by version 1.0 after testing is complete, no later than June 25, 2013.
     118
    90119= 0.45 =
    91120Minor testing and `readme.txt` changes only. No need to apply this upgrade.
    92121
    93122= 0.4 =
    94 Initial release.
     123Initial developmental release.
Note: See TracChangeset for help on using the changeset viewer.