Added ability to require CAPTCHA for guests when editing pages (AJAX INTERFACE ONLY)
authorDan
Thu, 27 Dec 2007 23:32:11 -0500
changeset 336 bfa2e9c23f03
parent 335 67bd3121a12e
child 337 491518997ae5
Added ability to require CAPTCHA for guests when editing pages (AJAX INTERFACE ONLY)
ajax.php
includes/clientside/static/acl.js
includes/clientside/static/editor.js
language/english/core.json
plugins/SpecialAdmin.php
--- a/ajax.php	Thu Dec 27 22:09:33 2007 -0500
+++ b/ajax.php	Thu Dec 27 23:32:11 2007 -0500
@@ -114,13 +114,24 @@
         $allowed = false;
         $src = '';
       }
+      
+      $auth_edit = ( $session->get_permissions('edit_page') && ( $session->get_permissions('even_when_protected') || !$paths->page_protected ) );
+      
       $return = array(
           'mode' => 'editor',
           'src' => $src,
           'auth_view_source' => $allowed,
-          'auth_edit' => $session->get_permissions('edit_page'),
-          'time' => time()
+          'auth_edit' => $auth_edit,
+          'time' => time(),
+          'require_captcha' => false,
         );
+      
+      if ( $auth_edit && !$session->user_logged_in && getConfig('guest_edit_require_captcha') == '1' )
+      {
+        $return['require_captcha'] = true;
+        $return['captcha_id'] = $session->make_captcha();
+      }
+      
       echo enano_json_encode($return);
       break;
     case "getpage":
@@ -178,6 +189,27 @@
         break;
       }
       
+      // Verify captcha, if needed
+      if ( !$session->user_logged_in && getConfig('guest_edit_require_captcha') == '1' )
+      {
+        if ( !isset($request['captcha_id']) || !isset($request['captcha_code']) )
+        {
+          die('Invalid request, need captcha metadata');
+        }
+        $code_correct = strtolower($session->get_captcha($request['captcha_id']));
+        $code_input = strtolower($request['captcha_code']);
+        if ( $code_correct !== $code_input )
+        {
+          $return = array(
+            'mode' => 'errors',
+            'errors' => array($lang->get('editor_err_captcha_wrong')),
+            'new_captcha' => $session->make_captcha()
+          );
+          echo enano_json_encode($return);
+          break;
+        }
+      }
+      
       // Verification complete. Start the PageProcessor and let it do the dirty work for us.
       $page = new PageProcessor($paths->page_id, $paths->namespace);
       if ( $page->update_page($request['src'], $request['summary'], ( $request['minor_edit'] == 1 )) )
@@ -197,6 +229,10 @@
           'mode' => 'errors',
           'errors' => array_values($errors)
           );
+        if ( !$session->user_logged_in && getConfig('guest_edit_require_captcha') == '1' )
+        {
+          $return['new_captcha'] = $session->make_captcha();
+        }
       }
       
       echo enano_json_encode($return);
--- a/includes/clientside/static/acl.js	Thu Dec 27 22:09:33 2007 -0500
+++ b/includes/clientside/static/acl.js	Thu Dec 27 23:32:11 2007 -0500
@@ -471,7 +471,7 @@
                 b.appendChild(document.createTextNode($lang.get('acl_lbl_delete_success_title')));
                 note.appendChild(b);
                 note.appendChild(document.createElement('br'));
-                note.appendChild(document.createTextNode($lang.get('acl_lbl_delete_success_title', { target_name: aclDataCache.target_name })));
+                note.appendChild(document.createTextNode($lang.get('acl_lbl_delete_success_body', { target_name: aclDataCache.target_name })));
                 note.appendChild(document.createElement('br'));
                 a = document.createElement('a');
                 a.href = '#';
--- a/includes/clientside/static/editor.js	Thu Dec 27 22:09:33 2007 -0500
+++ b/includes/clientside/static/editor.js	Thu Dec 27 23:32:11 2007 -0500
@@ -80,12 +80,15 @@
           return false;
         }
         
-        ajaxBuildEditor(response.src, (!response.auth_edit), response.time);
+        // do we need to enter a captcha before saving the page?
+        var captcha_hash = ( response.require_captcha ) ? response.captcha_id : false;
+        
+        ajaxBuildEditor(response.src, (!response.auth_edit), response.time, captcha_hash);
       }
     });
 }
 
-function ajaxBuildEditor(content, readonly, timestamp)
+function ajaxBuildEditor(content, readonly, timestamp, captcha_hash)
 {
   // Set flags
   // We don't want the fancy confirmation framework to trigger if the user is only viewing the page source
@@ -255,6 +258,47 @@
     tr2.appendChild(td2_1);
     tr2.appendChild(td2_2);
     
+    if ( captcha_hash )
+    {
+      // generate captcha field (effectively third row)
+      var tr4 = document.createElement('tr');
+      var td4_1 = document.createElement('td');
+      var td4_2 = document.createElement('td');
+      td4_1.className = 'row2';
+      td4_2.className = 'row1';
+      
+      td4_1.appendChild(document.createTextNode($lang.get('editor_lbl_field_captcha')));
+      td4_1.appendChild(document.createElement('br'));
+      var small2 = document.createElement('small');
+      small2.appendChild(document.createTextNode($lang.get('editor_msg_captcha_pleaseenter')));
+      small2.appendChild(document.createElement('br'));
+      small2.appendChild(document.createElement('br'));
+      small2.appendChild(document.createTextNode($lang.get('editor_msg_captcha_blind')));
+      td4_1.appendChild(small2);
+      
+      var img = document.createElement('img');
+      img.src = makeUrlNS('Special', 'Captcha/' + captcha_hash);
+      img._captchaHash = captcha_hash;
+      img.id = 'enano_editor_captcha_img';
+      img.onclick = function()
+      {
+        this.src = makeUrlNS('Special', 'Captcha/' + this._captchaHash + '/' + Math.floor(Math.random() * 100000));
+      }
+      img.style.cursor = 'pointer';
+      td4_2.appendChild(img);
+      td4_2.appendChild(document.createElement('br'));
+      td4_2.appendChild(document.createTextNode($lang.get('editor_lbl_field_captcha_code') + ' '));
+      var input = document.createElement('input');
+      input.type = 'text';
+      input.id = 'enano_editor_field_captcha';
+      input._captchaHash = captcha_hash;
+      input.size = '9';
+      td4_2.appendChild(input);
+      
+      tr4.appendChild(td4_1);
+      tr4.appendChild(td4_2);
+    }
+    
     // Third row: controls
     var tr3 = document.createElement('tr');
     var td3 = document.createElement('th');
@@ -302,6 +346,10 @@
     
     metatable.appendChild(tr1);
     metatable.appendChild(tr2);
+    if ( captcha_hash )
+    {
+      metatable.appendChild(tr4);
+    }
     metatable.appendChild(tr3);
   }
   tblholder.appendChild(metatable);
@@ -339,6 +387,14 @@
 {
   ajaxSetEditorLoading();
   var ta_content = $('ajaxEditArea').getContent();
+  
+  if ( ta_content == '' || ta_content == '<p></p>' || ta_content == '<p>&nbsp;</p>' )
+  {
+    new messagebox(MB_OK|MB_ICONSTOP, $lang.get('editor_err_no_text_title'), $lang.get('editor_err_no_text_body'));
+    ajaxUnSetEditorLoading();
+    return false;
+  }
+  
   var edit_summ = $('enano_editor_field_summary').object.value;
   if ( !edit_summ )
     edit_summ = '';
@@ -351,6 +407,21 @@
     minor_edit: is_minor,
     time: timestamp
   };
+  
+  // Do we need to add captcha info?
+  if ( document.getElementById('enano_editor_field_captcha') )
+  {
+    var captcha_field = document.getElementById('enano_editor_field_captcha');
+    if ( captcha_field.value == '' )
+    {
+      new messagebox(MB_OK|MB_ICONSTOP, $lang.get('editor_err_need_captcha_title'), $lang.get('editor_err_need_captcha_body'));
+      ajaxUnSetEditorLoading();
+      return false;
+    }
+    json_packet.captcha_code = captcha_field.value;
+    json_packet.captcha_id = captcha_field._captchaHash;
+  }
+  
   json_packet = ajaxEscape(toJSONString(json_packet));
   ajaxPost(stdAjaxPrefix + '&_mode=savepage_json', 'r=' + json_packet, function()
     {
@@ -374,6 +445,21 @@
         // This will be used if the PageProcessor generated errors (usually security/permissions related)
         if ( response.mode == 'errors' )
         {
+          // This will be true if the user entered a captcha code incorrectly, thus
+          // invalidating the code and requiring a new image to be generated.
+          if ( response.new_captcha )
+          {
+            // Generate the new captcha field
+            var img = document.getElementById('enano_editor_captcha_img');
+            var input = document.getElementById('enano_editor_field_captcha');
+            if ( img && input )
+            {
+              img._captchaHash = response.new_captcha;
+              input._captchaHash = response.new_captcha;
+              img.src = makeUrlNS('Special', 'Captcha/' + response.new_captcha);
+              input.value = '';
+            }
+          }
           var errors = '<ul><li>' + implode('</li><li>', response.errors) + '</li></ul>';
           new messagebox(MB_OK | MB_ICONSTOP, $lang.get('editor_err_save_title'), $lang.get('editor_err_save_body') + errors);
           return false;
@@ -392,14 +478,17 @@
           setAjaxLoading();
           editor_open = false;
           enableUnload();
+          changeOpac(0, 'ajaxEditContainer');
           ajaxGet(stdAjaxPrefix + '&_mode=getpage&noheaders', function()
             {
               if ( ajax.readyState == 4 )
               {
                 unsetAjaxLoading();
-                document.getElementById('ajaxEditContainer').innerHTML = '<div class="usermessage">' + $lang.get('editor_msg_saved') + '</div>' + ajax.responseText;
                 selectButtonMajor('article');
                 unselectAllButtonsMinor();
+                
+                document.getElementById('ajaxEditContainer').innerHTML = '<div class="usermessage">' + $lang.get('editor_msg_saved') + '</div>' + ajax.responseText;
+                opacity('ajaxEditContainer', 0, 100, 1000);
               }
             });
         }
--- a/language/english/core.json	Thu Dec 27 22:09:33 2007 -0500
+++ b/language/english/core.json	Thu Dec 27 23:32:11 2007 -0500
@@ -220,10 +220,15 @@
       err_save_body: 'A few problems were encountered while your page was being saved:',
       err_obsolete_title: 'Someone else modified this page while you were editing it',
       err_obsolete_body: 'While you were editing this page, %author% modified this page. The edit took place on %timestamp%. You can <a href="%page_url%" onclick="window.open(this.href); return false;">view the latest version of the page</a>, or click %this.editor_btn_save% again to replace the page with your revision.',
+      err_need_captcha_title: 'Missing confirmation code',
+      err_need_captcha_body: 'Please enter the confirmation code in the box labeled "%this.editor_lbl_field_captcha_code%". %this.editor_msg_captcha_blind%',
+      err_no_text_title: 'No text entered',
+      err_no_text_body: 'Please enter the text that will be in this page.',
       // Server-side errors
       err_no_rows: 'Page doesn\'t exist in the database',
       err_no_permission: 'You do not have permission to edit this page.',
       err_page_protected: 'This page is protected, and you do not have permission to edit protected pages.',
+      err_captcha_wrong: 'The confirmation code you entered is incorrect.',
       
       msg_saved: 'Your changes to this page have been saved.',
       msg_revert_confirm: 'Do you really want to revert your changes?',
@@ -252,6 +257,11 @@
       preview_blurb: '<b>Reminder:</b> This is only a preview - your changes to this page have not yet been saved.',
       msg_save_success_title: 'Changes saved',
       msg_save_success_body: 'Your changes to this page have been saved. Redirecting...',
+      
+      msg_captcha_pleaseenter: 'Please enter the code shown in the image to the right into the text box. This process helps to ensure that this page is not being edited by an automated bot. If the image to the right is illegible, you can regenerate it by clicking on the image.',
+      msg_captcha_blind: 'If you are visually impaired or otherwise cannot read the text shown to the right, please contact the site management and they will be able to make your requested edits.',
+      lbl_field_captcha: 'Visual confirmation',
+      lbl_field_captcha_code: 'Code:',
     },
     history: {
       summary_clearlogs: 'Automatic backup created when logs were purged',
--- a/plugins/SpecialAdmin.php	Thu Dec 27 22:09:33 2007 -0500
+++ b/plugins/SpecialAdmin.php	Thu Dec 27 23:32:11 2007 -0500
@@ -181,6 +181,8 @@
     if(isset($_POST['editmsg']))                 setConfig('wiki_edit_notice', '1');
     else                                         setConfig('wiki_edit_notice', '0');
     setConfig('wiki_edit_notice_text', $_POST['editmsg_text']);
+    if(isset($_POST['guest_edit_require_captcha'])) setConfig('guest_edit_require_captcha', '1');
+    else                                         setConfig('guest_edit_require_captcha', '0');
     
     // Stats
     if(isset($_POST['log_hits']))                setConfig('log_hits', '1');
@@ -341,6 +343,19 @@
         </td>
       </tr>
       
+      <tr>
+        <td class="row1">
+          <b>Require visual confirmation for guests editing pages</b><br />
+          If this is enabled, guests will be asked to enter a visual confirmation code before saving changes to a page.
+        </td>
+        <td class="row1">
+          <label>
+            <input type="checkbox" name="guest_edit_require_captcha" <?php if ( getConfig('guest_edit_require_captcha') == '1' ) echo 'checked="checked" '; ?>/>
+            Require guests to complete a CAPTCHA when editing pages
+          </label>
+        </td>
+      </tr>
+      
     <!-- Site statistics -->
     
       <tr><th class="subhead" colspan="2">Statistics and hit counting</th></tr>