New page protection UI. Both miniPrompt and failsafe HTML.
authorDan
Mon, 13 Apr 2009 14:43:28 -0400
changeset 906 c949e82b8f49
parent 905 1e40b33f2e3e
child 907 44851d7e9bda
New page protection UI. Both miniPrompt and failsafe HTML.
images/protect-icons.png
includes/clientside/static/ajax.js
includes/clientside/static/functions.js
includes/clientside/static/messagebox.js
includes/functions.php
includes/template.php
index.php
language/english/core.json
Binary file images/protect-icons.png has changed
--- a/includes/clientside/static/ajax.js	Mon Apr 13 12:32:36 2009 -0400
+++ b/includes/clientside/static/ajax.js	Mon Apr 13 14:43:28 2009 -0400
@@ -33,42 +33,134 @@
 
 // Miscellaneous AJAX applets
 
-window.ajaxProtect = function(l) {
+window.ajaxProtect = function(existing_level)
+{
   // IE <6 pseudo-compatibility
   if ( KILL_SWITCH )
     return true;
-  load_component('l10n');
+  
+  // touch this variable to allow it to be used in child functions
+  void(existing_level);
+  
+  load_component(['messagebox', 'jquery', 'jquery-ui', 'l10n', 'fadefilter', 'flyin']);
+  
+  // preload language
+  $lang.get('meta_meta');
   
-  if(shift) {
-    r = 'NO_REASON';
-  } else {
-    r = prompt($lang.get('ajax_protect_prompt_reason'));
-    if(!r || r=='') return;
+  var mp = miniPrompt(function(parent)
+    {
+      var icon_full = gen_sprite_html(cdnPath + '/images/protect-icons.png', 22, 22, 0, 0);
+      var icon_semi = gen_sprite_html(cdnPath + '/images/protect-icons.png', 22, 22, 22, 0);
+      var icon_none = gen_sprite_html(cdnPath + '/images/protect-icons.png', 22, 22, 44, 0);
+      
+      $(parent).append('<h3>' + $lang.get('onpage_protect_heading') + '</h3>');
+      $(parent).append('<p>' + $lang.get('onpage_protect_msg_select_level') + '</p>');
+      
+      $(parent).append('<div class="protectlevel"><label><input type="radio" id="protect_level_1" name="protect_level" /> ' + icon_full + ' ' + $lang.get('onpage_protect_btn_full') + '</label></div>');
+      $(parent).append('<div class="protectlevel_hint" id="protect_level_1_hint">' + $lang.get('onpage_protect_btn_full_hint') + '</div>');
+      $(parent).append('<div class="protectlevel"><label><input type="radio" id="protect_level_2" name="protect_level" /> ' + icon_semi + ' ' + $lang.get('onpage_protect_btn_semi') + '</label></div>');
+      $(parent).append('<div class="protectlevel_hint" id="protect_level_2_hint">' + $lang.get('onpage_protect_btn_semi_hint') + '</div>');
+      $(parent).append('<div class="protectlevel"><label><input type="radio" id="protect_level_0" name="protect_level" /> ' + icon_none + ' ' + $lang.get('onpage_protect_btn_none') + '</label></div>');
+      $(parent).append('<div class="protectlevel_hint" id="protect_level_0_hint">' + $lang.get('onpage_protect_btn_none_hint') + '</div>');
+      
+      $(parent).append('<table class="protectreason"><tr><td valign="top">' + $lang.get('onpage_protect_lbl_reason') + '</td><td><input id="protect_reason" size="30" type="text" /><br /><small>' + $lang.get('onpage_protect_lbl_reason_hint') + '</small></td></tr></table>');
+      
+      $(parent).append('<p class="buttons"><a class="submitbutton abutton abutton_green" style="font-weight: bold;" href="#" onclick="ajaxProtectSubmit(this); return false;">' + $lang.get('onpage_protect_btn_submit') + '</a> <a class="submitbutton abutton" href="#" onclick="miniPromptDestroy(this); return false;">' + $lang.get('etc_cancel') + '</a></p>');
+      
+      $('.protectlevel', parent).css('line-height', '22px');
+      $('h3', parent).css('text-align', 'center');
+      $('.protectlevel_hint', parent)
+        .css('font-size', 'smaller')
+        .css('margin-left', '52px')
+        .hide();
+      $('p.buttons', parent).css('margin-top', '15px').css('text-align', 'center');
+      
+      if ( typeof(existing_level) == 'number' )
+      {
+        $('#protect_level_' + existing_level, parent).attr('checked', 'checked');
+        $('#protect_level_' + existing_level + '_hint', parent).show();
+        $('#protect_level_' + existing_level, parent).parent().append(' <small><span style="color: #050; background-color: #eee; padding: 0 4px;">' + $lang.get('onpage_protect_lbl_current') + '</span></small>');
+      }
+      
+      $('input:radio', parent).click(function()
+        {
+          var mp = miniPromptGetParent(this);
+          $('.protectlevel_hint:visible', mp).hide('blind', 150);
+          $('#' + this.id + '_hint').show('blind', 150);
+          $('#protect_reason').focus();
+        });
+      $('input:text', parent).keyup(function(e)
+        {
+          if ( e.keyCode == 13 )
+            ajaxProtectSubmit(this);
+        });
+    });
+}
+
+window.ajaxProtectSubmit = function(el)
+{
+  var mp = miniPromptGetParent(el);
+  
+  var reason = trim($('#protect_reason', mp).attr('value'));
+  if ( reason == '' )
+  {
+    var oldbg = $('#protect_reason').css('background-color');
+    if ( jQuery.fx.off )
+    {
+      $('#protect_reason').css('background-color', '#a00');
+      setTimeout(function()
+        {
+          $('#protect_reason').css('background-color', oldbg);
+        }, 1000);
+    }
+    else
+    {
+      $('#protect_reason').css('background-color', '#a00').animate({ backgroundColor: oldbg }, 1000);
+    }
+    return false;
   }
-  setAjaxLoading();
-  document.getElementById('protbtn_0').style.textDecoration = 'none';
-  document.getElementById('protbtn_1').style.textDecoration = 'none';
-  document.getElementById('protbtn_2').style.textDecoration = 'none';
-  document.getElementById('protbtn_'+l).style.textDecoration = 'underline';
-  ajaxPost(stdAjaxPrefix+'&_mode=protect', 'reason='+ajaxEscape(r)+'&level='+l, function(ajax) {
-    if ( ajax.readyState == 4 && ajax.status == 200 ) {
-      unsetAjaxLoading();
-      if(ajax.responseText == 'good')
-        return true;
-      // check for JSON error response
-      var response = String(ajax.responseText + '');
-      if ( response.substr(0, 1) == '{' )
+  
+  var level = 0;
+  if ( $('#protect_level_1', mp).attr('checked') )
+    level = 1;
+  if ( $('#protect_level_2', mp).attr('checked') )
+    level = 2;
+  
+  var whitey = whiteOutMiniPrompt(mp);
+  $.post(stdAjaxPrefix + '&_mode=protect', { level: level, reason: reason }, function(response, statustext)
+    {
+      if ( response.success )
       {
-        response = parseJSON(response);
-        if ( response.mode == 'error' )
+        whiteOutReportSuccess(whitey);
+        setTimeout(function()
+          {
+            miniPromptDestroy(mp);
+          }, 1250);
+        // update protect button
+        var btn = $('#tb_ajax_protect_btn').get(0);
+        btn.level = level;
+        btn.setAttribute('onclick', null);
+        btn.onclick = null;
+        $(btn).click(function()
+          {
+            ajaxProtect(this.level);
+            return false;
+          });
+        var status = '';
+        switch(level)
         {
-          alert(response.error);
-          return true;
+          case 1: status = $lang.get('onpage_btn_protect_on'); break;
+          case 0: status = $lang.get('onpage_btn_protect_off'); break;
+          case 2: status = $lang.get('onpage_btn_protect_semi'); break;
         }
+        $('#tb_ajax_protect_status').text(status);
       }
-      alert(ajax.responseText);
-    }
-  }, true);
+      else
+      {
+        whiteOutReportFailure(whitey);
+        alert($lang.get('page_err_' + response.error));
+      }
+    }, 'json');
 }
 
 window.ajaxRename = function()
--- a/includes/clientside/static/functions.js	Mon Apr 13 12:32:36 2009 -0400
+++ b/includes/clientside/static/functions.js	Mon Apr 13 14:43:28 2009 -0400
@@ -465,7 +465,7 @@
 function gen_sprite(path, width, height, xpos, ypos)
 {
   var image = document.createElement('img');
-  image.src = scriptPath + '/images/spacer.gif';
+  image.src = cdnPath + '/images/spacer.gif';
   image.width = String(width);
   image.height = String(height);
   image.style.backgroundImage = 'url(' + path + ')';
--- a/includes/clientside/static/messagebox.js	Mon Apr 13 12:32:36 2009 -0400
+++ b/includes/clientside/static/messagebox.js	Mon Apr 13 14:43:28 2009 -0400
@@ -572,6 +572,39 @@
     });
 }
 
+/**
+ * Identical to whiteOutElement(), but safe to call on miniPrompt divs.
+ */
+
+function whiteOutMiniPrompt(el)
+{
+  var top = getScrollOffset();
+  var left = ( getWidth() / 2 ) - ( 320 / 2);
+  var width = 320;
+  var height = $dynano(el).Height() - 58;
+  
+  var blackout = document.createElement('div');
+  // using fixed here allows modal windows to be blacked out
+  blackout.style.position = ( el.style.position == 'fixed' ) ? 'fixed' : 'absolute';
+  blackout.style.top = top + 'px';
+  blackout.style.left = left + 'px';
+  blackout.style.width = width + 'px';
+  blackout.style.height = height + 'px';
+  
+  blackout.style.backgroundColor = '#FFFFFF';
+  domObjChangeOpac(60, blackout);
+  var background = ( $dynano(el).Height() < 48 ) ? 'url(' + scriptPath + '/images/loading.gif)' : 'url(' + scriptPath + '/includes/clientside/tinymce/themes/advanced/skins/default/img/progress.gif)';
+  blackout.style.backgroundImage = background;
+  blackout.style.backgroundPosition = 'center center';
+  blackout.style.backgroundRepeat = 'no-repeat';
+  blackout.style.zIndex = '1000';
+  
+  var body = document.getElementsByTagName('body')[0];
+  body.appendChild(blackout);
+  
+  return blackout;
+}
+
 function testMPMessageBox()
 {
   miniPromptMessage({
--- a/includes/functions.php	Mon Apr 13 12:32:36 2009 -0400
+++ b/includes/functions.php	Mon Apr 13 14:43:28 2009 -0400
@@ -2155,6 +2155,27 @@
 }
 
 /**
+ * Generate HTML for a sprite image.
+ * @param string Path to sprite image
+ * @param int Width of resulting image
+ * @param int Height of resulting image
+ * @param int X offset
+ * @param int Y offset
+ * @return object HTMLImageElement
+ */
+
+function gen_sprite($path, $width, $height, $xpos, $ypos)
+{
+  $html = '<img src="' . scriptPath . '/images/spacer.gif" width="' . $width . '" height="' . $height . '" ';
+  $xpos = ( $xpos == 0 ) ? '0' : '-' . strval($xpos);
+  $ypos = ( $ypos == 0 ) ? '0' : '-' . strval($ypos);
+  $html .= 'style="background-image: url(' . $path . '); background-repeat: no-repeat; background-position: ' . $ypos . 'px ' . $xpos . 'px;"';
+  $html .= ' />';
+  
+  return $html;
+}
+
+/**
  * Portal function allowing spam-filtering plugins.
  * Hooking guide:
  *   - Attach to spam_check
--- a/includes/template.php	Mon Apr 13 12:32:36 2009 -0400
+++ b/includes/template.php	Mon Apr 13 14:43:28 2009 -0400
@@ -732,53 +732,28 @@
     // Protect button
     if($perms->get_permissions('read') && $session->check_acl_scope('protect', $local_namespace) && $paths->wiki_mode && $local_page_exists && $perms->get_permissions('protect'))
     {
+      switch($local_cdata['protected'])
+      {
+        case PROTECT_FULL: $protect_status = $lang->get('onpage_btn_protect_on'); break;
+        case PROTECT_SEMI: $protect_status = $lang->get('onpage_btn_protect_semi'); break;
+        case PROTECT_NONE: $protect_status = $lang->get('onpage_btn_protect_off'); break;
+      }
       
       $label = $this->makeParserText($tplvars['toolbar_label']);
-      $label->assign_vars(array('TEXT' => $lang->get('onpage_lbl_protect')));
+      $label->assign_vars(array('TEXT' => $lang->get('onpage_lbl_protect') . ' ' . "<b><span id=\"tb_ajax_protect_status\">$protect_status</span></b>"));
       $t0 = $label->run();
       
-      $ctmp = ''; 
-      if ( $local_cdata['protected'] == 1 )
-      {
-        $ctmp=' style="text-decoration: underline;"';
-      }
       $menubtn->assign_vars(array(
-          'FLAGS' => 'accesskey="i" onclick="if ( !KILL_SWITCH ) { ajaxProtect(1); return false; }" id="protbtn_1" title="' . $lang->get('onpage_tip_protect_on') . '"'.$ctmp,
-          'HREF'  => makeUrl($local_page, 'do=protect&level=1', true),
-          'TEXT'  => $lang->get('onpage_btn_protect_on')
+          'FLAGS' => 'accesskey="p" onclick="ajaxProtect(' . $local_cdata['protected'] . '); return false;" id="tb_ajax_protect_btn" title="' . $lang->get('onpage_tip_protect') . '"',
+          'HREF' => makeUrl($local_page, 'do=protect', true),
+          'TEXT' => $lang->get('onpage_btn_protect_change')
         ));
       $t1 = $menubtn->run();
       
-      $ctmp = '';
-      if ( $local_cdata['protected'] == 0 )
-      {
-        $ctmp=' style="text-decoration: underline;"';
-      }
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'accesskey="o" onclick="if ( !KILL_SWITCH ) { ajaxProtect(0); return false; }" id="protbtn_0" title="' . $lang->get('onpage_tip_protect_off') . '"'.$ctmp,
-          'HREF'  => makeUrl($local_page, 'do=protect&level=0', true),
-          'TEXT'  => $lang->get('onpage_btn_protect_off')
-        ));
-      $t2 = $menubtn->run();
-      
-      $ctmp = '';
-      if ( $local_cdata['protected'] == 2 )
-      {
-        $ctmp = ' style="text-decoration: underline;"';
-      }
-      $menubtn->assign_vars(array(
-          'FLAGS' => 'accesskey="p" onclick="if ( !KILL_SWITCH ) { ajaxProtect(2); return false; }" id="protbtn_2" title="' . $lang->get('onpage_tip_protect_semi') . '"'.$ctmp,
-          'HREF'  => makeUrl($local_page, 'do=protect&level=2', true),
-          'TEXT'  => $lang->get('onpage_btn_protect_semi')
-        ));
-      $t3 = $menubtn->run();
-      
       $this->toolbar_menu .= '        <table border="0" cellspacing="0" cellpadding="0">
           <tr>
             <td>'.$t0.'</td>
             <td>'.$t1.'</td>
-            <td>'.$t2.'</td>
-            <td>'.$t3.'</td>
           </tr>
         </table>';
     }
--- a/index.php	Mon Apr 13 12:32:36 2009 -0400
+++ b/index.php	Mon Apr 13 14:43:28 2009 -0400
@@ -329,39 +329,92 @@
       $template->footer();
       break;
     case 'protect':
-      if (!isset($_REQUEST['level'])) die_friendly('Invalid request', '<p>No protection level specified</p>');
-      require_once(ENANO_ROOT.'/includes/pageutils.php');
-      if(!empty($_POST['reason']))
+      if ( isset($_POST['level']) && isset($_POST['reason']) )
       {
-        if(!preg_match('#^([0-2]*){1}$#', $_POST['level'])) die_friendly('Error protecting page', '<p>Request validation failed</p>');
-        PageUtils::protect($paths->page_id, $paths->namespace, intval($_POST['level']), $_POST['reason']);
+        $level = intval($_POST['level']);
+        if ( !in_array($level, array(PROTECT_FULL, PROTECT_SEMI, PROTECT_NONE)) )
+        {
+          $errors[] = 'bad level';
+        }
+        $reason = trim($_POST['reason']);
+        if ( empty($reason) )
+        {
+          $errors[] = $lang->get('onpage_protect_err_need_reason');
+        }
         
-        die_friendly($lang->get('page_protect_lbl_success_title'), '<p>' . $lang->get('page_protect_lbl_success_body', array( 'page_link' => makeUrl($paths->page) )) . '</p>');
+        $page = new PageProcessor($paths->page_id, $paths->namespace);
+        $result = $page->protect_page($level, $reason);
+        if ( $result['success'] )
+        {
+          redirect(makeUrl($paths->page), $lang->get('page_protect_lbl_success_title'), $lang->get('page_protect_lbl_success_body', array('page_link' => makeUrl($paths->page, false, true))), 3);
+        }
+        else
+        {
+          $errors[] = $lang->get('page_err_' . $result['error']);
+        }
       }
       $template->header();
       ?>
       <form action="<?php echo makeUrl($paths->page, 'do=protect'); ?>" method="post">
-        <input type="hidden" name="level" value="<?php echo $_REQUEST['level']; ?>" />
-        <?php if(isset($_POST['reason'])) echo '<p style="color: red;">' . $lang->get('page_protect_err_need_reason') . '</p>'; ?>
-        <p><?php echo $lang->get('page_protect_lbl_reason'); ?></p>
-        <p><input type="text" name="reason" size="40" /><br />
-           <?php echo $lang->get('page_protect_lbl_level'); ?> <b><?php
-             switch($_REQUEST['level'])
-             {
-               case '0':
-                 echo $lang->get('page_protect_lbl_level_none');
-                 break;
-               case '1':
-                 echo $lang->get('page_protect_lbl_level_full');
-                 break;
-               case '2':
-                 echo $lang->get('page_protect_lbl_level_semi');
-                 break;
-               default:
-                 echo 'None;</b> Warning: request validation will fail after clicking submit<b>';
-             }
-           ?></b></p>
-        <p><input type="submit" value="<?php echo htmlspecialchars($lang->get('page_protect_btn_submit')) ?>" style="font-weight: bold;" /></p> 
+        <h3><?php echo $lang->get('onpage_protect_heading'); ?></h3>
+        <p><?php echo $lang->get('onpage_protect_msg_select_level'); ?></p>
+        
+        <?php
+        if ( !empty($errors) )
+        {
+          echo '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>';
+        }
+        ?>
+        
+        <div class="protectlevel" style="line-height: 22px; margin-left: 17px;">
+          <label>
+            <input type="radio" name="level" value="<?php echo PROTECT_FULL; ?>" />
+            <?php echo gen_sprite(cdnPath . '/images/protect-icons.png', 22, 22, 0, 0); ?>
+            <?php echo $lang->get('onpage_protect_btn_full'); ?>
+          </label>
+        </div>
+        <div class="protectlevel_hint" style="font-size: smaller; margin-left: 68px;">
+          <?php echo $lang->get('onpage_protect_btn_full_hint'); ?>
+        </div>
+        
+        <div class="protectlevel" style="line-height: 22px; margin-left: 17px;">
+          <label>
+            <input type="radio" name="level" value="<?php echo PROTECT_SEMI; ?>" />
+            <?php echo gen_sprite(cdnPath . '/images/protect-icons.png', 22, 22, 22, 0); ?>
+            <?php echo $lang->get('onpage_protect_btn_semi'); ?>
+          </label>
+        </div>
+        <div class="protectlevel_hint" style="font-size: smaller; margin-left: 68px;">
+          <?php echo $lang->get('onpage_protect_btn_semi_hint'); ?>
+        </div>
+        
+        <div class="protectlevel" style="line-height: 22px; margin-left: 17px;">
+          <label>
+            <input type="radio" name="level" value="<?php echo PROTECT_NONE; ?>" />
+            <?php echo gen_sprite(cdnPath . '/images/protect-icons.png', 22, 22, 44, 0); ?>
+            <?php echo $lang->get('onpage_protect_btn_none'); ?>
+          </label>
+        </div>
+        <div class="protectlevel_hint" style="font-size: smaller; margin-left: 68px;">
+          <?php echo $lang->get('onpage_protect_btn_none_hint'); ?>
+        </div>
+        
+        <table style="margin-left: 1em;" cellspacing="10">
+          <tr>
+            <td valign="top">
+              <?php echo $lang->get('onpage_protect_lbl_reason'); ?>
+            </td>
+            <td>
+              <input type="text" name="reason" size="40" /><br />
+              <small><?php echo $lang->get('onpage_protect_lbl_reason_hint'); ?></small>
+            </td>
+          </tr>
+        </table>
+                              
+        <p>
+          <input type="submit" value="<?php echo htmlspecialchars($lang->get('page_protect_btn_submit')) ?>" style="font-weight: bold;" />
+          <a class="abutton" href="<?php echo makeUrl($paths->page, false, true); ?>"><?php echo $lang->get('etc_cancel'); ?></a>
+        </p> 
       </form>
       <?php
       $template->footer();
--- a/language/english/core.json	Mon Apr 13 12:32:36 2009 -0400
+++ b/language/english/core.json	Mon Apr 13 14:43:28 2009 -0400
@@ -71,13 +71,6 @@
       
       protect_lbl_success_title: 'Page protected',
       protect_lbl_success_body: 'The protection setting has been applied. <a href="%page_link%">Return to the page</a>.',
-      protect_err_need_reason: 'Error: you must enter a reason for protecting this page.',
-      protect_lbl_reason: 'Reason for protecting the page:',
-      protect_lbl_level: 'Protecion level to be applied:',
-      protect_lbl_level_none: 'No protection',
-      protect_lbl_level_semi: 'Semi-protection',
-      protect_lbl_level_full: 'Full protection',
-      protect_btn_submit: 'Protect page',
       
       rename_err_need_name: 'Error: you must enter a new name for this page.',
       rename_lbl: 'Please enter a new name for this page:',
@@ -286,6 +279,7 @@
       btn_protect_on: 'on',
       btn_protect_off: 'off',
       btn_protect_semi: 'semi',
+      btn_protect_change: 'change',
       btn_clearlogs: 'clear page logs',
       btn_deletepage: 'delete this page',
       btn_deletepage_votes: ' (<b>%num_votes%</b> vote%plural%)',
@@ -303,9 +297,7 @@
       tip_delvote: 'Vote to have this page deleted (alt-d)',
       tip_resetvotes: 'Clear the list of votes for deletion against this page (alt-y)',
       tip_printable: 'View a version of this page that is suitable for printing',
-      tip_protect_on: 'Prevents all non-administrators from editing this page. [alt-i]',
-      tip_protect_off: 'Allows everyone to edit this page. [alt-o]',
-      tip_protect_semi: 'Allows only users who have been registered for 4 days to edit this page. [alt-p]',
+      tip_protect: 'Change the protection level of this page (alt-p)',
       tip_flushlogs: 'Remove all edit and action logs for this page from the database. IRREVERSIBLE! (alt-l)',
       tip_deletepage: 'Delete this page. This is always reversible unless the logs are cleared. (alt-k)',
       tip_adminoptions: 'Administrative options for this page',
@@ -329,7 +321,21 @@
       filebox_heading_history: 'File history',
       filebox_btn_this_version: 'this ver',
       filebox_btn_revert: 'restore',
-      filebox_btn_current: 'current'
+      filebox_btn_current: 'current',
+      
+      protect_heading: 'Protect page',
+      protect_msg_select_level: 'Select a protection level:',
+      protect_btn_full: 'Full protection',
+      protect_btn_full_hint: 'Prevents everyone except moderators and administrators (by default) from editing the page.',
+      protect_btn_semi: 'Semi-protection',
+      protect_btn_semi_hint: 'Only users who have been members of this site for more than 4 days will be able to edit this page.',
+      protect_btn_none: 'No protection',
+      protect_btn_none_hint: 'Allows everybody to edit this page unless ACLs deny them.',
+      protect_lbl_current: 'current setting',
+      protect_lbl_reason: 'Reason:',
+      protect_lbl_reason_hint: 'Enter a short reason for protecting the page. The message you enter here will be displayed in the page\'s logs.',
+      protect_btn_submit: 'Protect page',
+      protect_err_need_reason: 'Please enter a reason for protecting this page.',
     },
     editor: {
       err_server: 'There was a problem starting the editor',