Hotfix - Version 1.2

Version Description

  • Lets themes enqueue comment-reply.js in the HTML header without JavaScript errors.
Download this release

Release Info

Developer peterwilsoncc
Plugin Icon wp plugin Hotfix
Version 1.2
Comparing to
See all releases

Code changes from version 1.1 to 1.2

Files changed (4) hide show
  1. hotfix.php +21 -5
  2. js/comment-reply.js +391 -0
  3. js/comment-reply.min.js +1 -0
  4. readme.txt +7 -8
hotfix.php CHANGED
@@ -2,7 +2,7 @@
2
  /*
3
  Plugin Name: Hotfix
4
  Description: Provides "hotfixes" for selected WordPress bugs, so you don't have to wait for the next WordPress core release. Keep the plugin updated!
5
- Version: 1.1
6
  Author: Mark Jaquith
7
  Author URI: http://coveredwebservices.com/
8
  */
@@ -11,19 +11,19 @@ Author URI: http://coveredwebservices.com/
11
  WP_Hotfix_Controller::init();
12
 
13
  class WP_Hotfix_Controller {
14
- function init() {
15
  add_action( 'init', 'wp_hotfix_init' );
16
  register_activation_hook( __FILE__, array( __CLASS__, 'activate' ) );
17
  register_deactivation_hook( __FILE__, array( __CLASS__, 'deactivate' ) );
18
  }
19
- function activate() {
20
  add_option( 'hotfix_version', '1' );
21
  register_uninstall_hook( __FILE__, array( __CLASS__, 'uninstall' ) );
22
  }
23
- function deactivate() {
24
  delete_option( 'hotfix_version' );
25
  }
26
- function uninstall() {
27
  self::deactivate(); // The same, for now
28
  }
29
  }
@@ -34,6 +34,9 @@ function wp_hotfix_init() {
34
  $hotfixes = array();
35
 
36
  switch ( $wp_version ) {
 
 
 
37
  case '3.5' :
38
  $hotfixes = array( '350_twentytwelve', '350_iis_http_failure' );
39
  break;
@@ -238,3 +241,16 @@ function wp_hotfix_350_iis_http_failure_get_temp_dir() {
238
  return $temp;
239
  return false;
240
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  /*
3
  Plugin Name: Hotfix
4
  Description: Provides "hotfixes" for selected WordPress bugs, so you don't have to wait for the next WordPress core release. Keep the plugin updated!
5
+ Version: 1.2
6
  Author: Mark Jaquith
7
  Author URI: http://coveredwebservices.com/
8
  */
11
  WP_Hotfix_Controller::init();
12
 
13
  class WP_Hotfix_Controller {
14
+ public static function init() {
15
  add_action( 'init', 'wp_hotfix_init' );
16
  register_activation_hook( __FILE__, array( __CLASS__, 'activate' ) );
17
  register_deactivation_hook( __FILE__, array( __CLASS__, 'deactivate' ) );
18
  }
19
+ public static function activate() {
20
  add_option( 'hotfix_version', '1' );
21
  register_uninstall_hook( __FILE__, array( __CLASS__, 'uninstall' ) );
22
  }
23
+ public static function deactivate() {
24
  delete_option( 'hotfix_version' );
25
  }
26
+ public static function uninstall() {
27
  self::deactivate(); // The same, for now
28
  }
29
  }
34
  $hotfixes = array();
35
 
36
  switch ( $wp_version ) {
37
+ case '5.1' :
38
+ $hotfixes = array( '510_comment_reply_js' );
39
+ break;
40
  case '3.5' :
41
  $hotfixes = array( '350_twentytwelve', '350_iis_http_failure' );
42
  break;
241
  return $temp;
242
  return false;
243
  }
244
+
245
+ function wp_hotfix_510_comment_reply_js() {
246
+ add_action( 'wp_enqueue_scripts', 'wp_hotfix_510_enqueue_comment_reply_js', 99 );
247
+ }
248
+
249
+ function wp_hotfix_510_enqueue_comment_reply_js() {
250
+ global $wp_scripts;
251
+
252
+ $suffix = SCRIPT_DEBUG ? '' : '.min';
253
+
254
+ $wp_scripts->registered['comment-reply']->src = plugins_url( "js/comment-reply$suffix.js", __FILE__ );
255
+ $wp_scripts->registered['comment-reply']->ver = 'wp46280';
256
+ }
js/comment-reply.js ADDED
@@ -0,0 +1,391 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Handles the addition of the comment form.
3
+ *
4
+ * @since 2.7.0
5
+ * @output wp-includes/js/comment-reply.js
6
+ *
7
+ * @namespace addComment
8
+ *
9
+ * @type {Object}
10
+ */
11
+ window.addComment = ( function( window ) {
12
+ // Avoid scope lookups on commonly used variables.
13
+ var document = window.document;
14
+
15
+ // Settings.
16
+ var config = {
17
+ commentReplyClass : 'comment-reply-link',
18
+ cancelReplyId : 'cancel-comment-reply-link',
19
+ commentFormId : 'commentform',
20
+ temporaryFormId : 'wp-temp-form-div',
21
+ parentIdFieldId : 'comment_parent',
22
+ postIdFieldId : 'comment_post_ID'
23
+ };
24
+
25
+ // Cross browser MutationObserver.
26
+ var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
27
+
28
+ // Check browser cuts the mustard.
29
+ var cutsTheMustard = 'querySelector' in document && 'addEventListener' in window;
30
+
31
+ /*
32
+ * Check browser supports dataset.
33
+ * !! sets the variable to true if the property exists.
34
+ */
35
+ var supportsDataset = !! document.documentElement.dataset;
36
+
37
+ // For holding the cancel element.
38
+ var cancelElement;
39
+
40
+ // For holding the comment form element.
41
+ var commentFormElement;
42
+
43
+ // The respond element.
44
+ var respondElement;
45
+
46
+ // The mutation observer.
47
+ var observer;
48
+
49
+ if ( cutsTheMustard && document.readyState !== 'loading' ) {
50
+ ready();
51
+ } else if ( cutsTheMustard ) {
52
+ window.addEventListener( 'DOMContentLoaded', ready, false );
53
+ }
54
+
55
+ /**
56
+ * Sets up object variables after the DOM is ready.
57
+ *
58
+ * @since 5.1.0
59
+ */
60
+ function ready() {
61
+ // Initialise the events.
62
+ init();
63
+
64
+ // Set up a MutationObserver to check for comments loaded late.
65
+ observeChanges();
66
+ }
67
+
68
+ /**
69
+ * Add events to links classed .comment-reply-link.
70
+ *
71
+ * Searches the context for reply links and adds the JavaScript events
72
+ * required to move the comment form. To allow for lazy loading of
73
+ * comments this method is exposed as window.commentReply.init().
74
+ *
75
+ * @since 5.1.0
76
+ *
77
+ * @memberOf addComment
78
+ *
79
+ * @param {HTMLElement} context The parent DOM element to search for links.
80
+ */
81
+ function init( context ) {
82
+ if ( ! cutsTheMustard ) {
83
+ return;
84
+ }
85
+
86
+ // Get required elements.
87
+ cancelElement = getElementById( config.cancelReplyId );
88
+ commentFormElement = getElementById( config.commentFormId );
89
+
90
+ // No cancel element, no replies.
91
+ if ( ! cancelElement ) {
92
+ return;
93
+ }
94
+
95
+ cancelElement.addEventListener( 'touchstart', cancelEvent );
96
+ cancelElement.addEventListener( 'click', cancelEvent );
97
+
98
+ var links = replyLinks( context );
99
+ var element;
100
+
101
+ for ( var i = 0, l = links.length; i < l; i++ ) {
102
+ element = links[i];
103
+
104
+ element.addEventListener( 'touchstart', clickEvent );
105
+ element.addEventListener( 'click', clickEvent );
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Return all links classed .comment-reply-link.
111
+ *
112
+ * @since 5.1.0
113
+ *
114
+ * @param {HTMLElement} context The parent DOM element to search for links.
115
+ *
116
+ * @return {HTMLCollection|NodeList|Array}
117
+ */
118
+ function replyLinks( context ) {
119
+ var selectorClass = config.commentReplyClass;
120
+ var allReplyLinks;
121
+
122
+ // childNodes is a handy check to ensure the context is a HTMLElement.
123
+ if ( ! context || ! context.childNodes ) {
124
+ context = document;
125
+ }
126
+
127
+ if ( document.getElementsByClassName ) {
128
+ // Fastest.
129
+ allReplyLinks = context.getElementsByClassName( selectorClass );
130
+ }
131
+ else {
132
+ // Fast.
133
+ allReplyLinks = context.querySelectorAll( '.' + selectorClass );
134
+ }
135
+
136
+ return allReplyLinks;
137
+ }
138
+
139
+ /**
140
+ * Cancel event handler.
141
+ *
142
+ * @since 5.1.0
143
+ *
144
+ * @param {Event} event The calling event.
145
+ */
146
+ function cancelEvent( event ) {
147
+ var cancelLink = this;
148
+ var temporaryFormId = config.temporaryFormId;
149
+ var temporaryElement = getElementById( temporaryFormId );
150
+
151
+ if ( ! temporaryElement || ! respondElement ) {
152
+ // Conditions for cancel link fail.
153
+ return;
154
+ }
155
+
156
+ getElementById( config.parentIdFieldId ).value = '0';
157
+
158
+ // Move the respond form back in place of the temporary element.
159
+ temporaryElement.parentNode.replaceChild( respondElement ,temporaryElement );
160
+ cancelLink.style.display = 'none';
161
+ event.preventDefault();
162
+ }
163
+
164
+ /**
165
+ * Click event handler.
166
+ *
167
+ * @since 5.1.0
168
+ *
169
+ * @param {Event} event The calling event.
170
+ */
171
+ function clickEvent( event ) {
172
+ var replyLink = this,
173
+ commId = getDataAttribute( replyLink, 'belowelement'),
174
+ parentId = getDataAttribute( replyLink, 'commentid' ),
175
+ respondId = getDataAttribute( replyLink, 'respondelement'),
176
+ postId = getDataAttribute( replyLink, 'postid'),
177
+ follow;
178
+
179
+ if ( ! commId || ! parentId || ! respondId || ! postId ) {
180
+ /*
181
+ * Theme or plugin defines own link via custom `wp_list_comments()` callback
182
+ * and calls `moveForm()` either directly or via a custom event hook.
183
+ */
184
+ return;
185
+ }
186
+
187
+ /*
188
+ * Third party comments systems can hook into this function via the global scope,
189
+ * therefore the click event needs to reference the global scope.
190
+ */
191
+ follow = window.addComment.moveForm(commId, parentId, respondId, postId);
192
+ if ( false === follow ) {
193
+ event.preventDefault();
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Creates a mutation observer to check for newly inserted comments.
199
+ *
200
+ * @since 5.1.0
201
+ */
202
+ function observeChanges() {
203
+ if ( ! MutationObserver ) {
204
+ return;
205
+ }
206
+
207
+ var observerOptions = {
208
+ childList: true,
209
+ subTree: true
210
+ };
211
+
212
+ observer = new MutationObserver( handleChanges );
213
+ observer.observe( document.body, observerOptions );
214
+ }
215
+
216
+ /**
217
+ * Handles DOM changes, calling init() if any new nodes are added.
218
+ *
219
+ * @since 5.1.0
220
+ *
221
+ * @param {Array} mutationRecords Array of MutationRecord objects.
222
+ */
223
+ function handleChanges( mutationRecords ) {
224
+ var i = mutationRecords.length;
225
+
226
+ while ( i-- ) {
227
+ // Call init() once if any record in this set adds nodes.
228
+ if ( mutationRecords[ i ].addedNodes.length ) {
229
+ init();
230
+ return;
231
+ }
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Backward compatible getter of data-* attribute.
237
+ *
238
+ * Uses element.dataset if it exists, otherwise uses getAttribute.
239
+ *
240
+ * @since 5.1.0
241
+ *
242
+ * @param {HTMLElement} Element DOM element with the attribute.
243
+ * @param {String} Attribute the attribute to get.
244
+ *
245
+ * @return {String}
246
+ */
247
+ function getDataAttribute( element, attribute ) {
248
+ if ( supportsDataset ) {
249
+ return element.dataset[attribute];
250
+ }
251
+ else {
252
+ return element.getAttribute( 'data-' + attribute );
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Get element by ID.
258
+ *
259
+ * Local alias for document.getElementById.
260
+ *
261
+ * @since 5.1.0
262
+ *
263
+ * @param {HTMLElement} The requested element.
264
+ */
265
+ function getElementById( elementId ) {
266
+ return document.getElementById( elementId );
267
+ }
268
+
269
+ /**
270
+ * Moves the reply form from its current position to the reply location.
271
+ *
272
+ * @since 2.7.0
273
+ *
274
+ * @memberOf addComment
275
+ *
276
+ * @param {String} addBelowId HTML ID of element the form follows.
277
+ * @param {String} commentId Database ID of comment being replied to.
278
+ * @param {String} respondId HTML ID of 'respond' element.
279
+ * @param {String} postId Database ID of the post.
280
+ */
281
+ function moveForm( addBelowId, commentId, respondId, postId ) {
282
+ // Get elements based on their IDs.
283
+ var addBelowElement = getElementById( addBelowId );
284
+ respondElement = getElementById( respondId );
285
+
286
+ // Get the hidden fields.
287
+ var parentIdField = getElementById( config.parentIdFieldId );
288
+ var postIdField = getElementById( config.postIdFieldId );
289
+ var element, cssHidden, style;
290
+
291
+ if ( ! addBelowElement || ! respondElement || ! parentIdField ) {
292
+ // Missing key elements, fail.
293
+ return;
294
+ }
295
+
296
+ addPlaceHolder( respondElement );
297
+
298
+ // Set the value of the post.
299
+ if ( postId && postIdField ) {
300
+ postIdField.value = postId;
301
+ }
302
+
303
+ parentIdField.value = commentId;
304
+
305
+ cancelElement.style.display = '';
306
+ addBelowElement.parentNode.insertBefore( respondElement, addBelowElement.nextSibling );
307
+
308
+ /*
309
+ * This is for backward compatibility with third party commenting systems
310
+ * hooking into the event using older techniques.
311
+ */
312
+ cancelElement.onclick = function(){
313
+ return false;
314
+ };
315
+
316
+ // Focus on the first field in the comment form.
317
+ try {
318
+ for ( var i = 0; i < commentFormElement.elements.length; i++ ) {
319
+ element = commentFormElement.elements[i];
320
+ cssHidden = false;
321
+
322
+ // Get elements computed style.
323
+ if ( 'getComputedStyle' in window ) {
324
+ // Modern browsers.
325
+ style = window.getComputedStyle( element );
326
+ } else if ( document.documentElement.currentStyle ) {
327
+ // IE 8.
328
+ style = element.currentStyle;
329
+ }
330
+
331
+ /*
332
+ * For display none, do the same thing jQuery does. For visibility,
333
+ * check the element computed style since browsers are already doing
334
+ * the job for us. In fact, the visibility computed style is the actual
335
+ * computed value and already takes into account the element ancestors.
336
+ */
337
+ if ( ( element.offsetWidth <= 0 && element.offsetHeight <= 0 ) || style.visibility === 'hidden' ) {
338
+ cssHidden = true;
339
+ }
340
+
341
+ // Skip form elements that are hidden or disabled.
342
+ if ( 'hidden' === element.type || element.disabled || cssHidden ) {
343
+ continue;
344
+ }
345
+
346
+ element.focus();
347
+ // Stop after the first focusable element.
348
+ break;
349
+ }
350
+ }
351
+ catch(e) {
352
+
353
+ }
354
+
355
+ /*
356
+ * false is returned for backward compatibility with third party commenting systems
357
+ * hooking into this function.
358
+ */
359
+ return false;
360
+ }
361
+
362
+ /**
363
+ * Add placeholder element.
364
+ *
365
+ * Places a place holder element above the #respond element for
366
+ * the form to be returned to if needs be.
367
+ *
368
+ * @since 2.7.0
369
+ *
370
+ * @param {HTMLelement} respondElement the #respond element holding comment form.
371
+ */
372
+ function addPlaceHolder( respondElement ) {
373
+ var temporaryFormId = config.temporaryFormId;
374
+ var temporaryElement = getElementById( temporaryFormId );
375
+
376
+ if ( temporaryElement ) {
377
+ // The element already exists, no need to recreate.
378
+ return;
379
+ }
380
+
381
+ temporaryElement = document.createElement( 'div' );
382
+ temporaryElement.id = temporaryFormId;
383
+ temporaryElement.style.display = 'none';
384
+ respondElement.parentNode.insertBefore( temporaryElement, respondElement );
385
+ }
386
+
387
+ return {
388
+ init: init,
389
+ moveForm: moveForm
390
+ };
391
+ })( window );
js/comment-reply.min.js ADDED
@@ -0,0 +1 @@
 
1
+ window.addComment=function(a){function b(){c(),g()}function c(a){if(t&&(m=j(r.cancelReplyId),n=j(r.commentFormId),m)){m.addEventListener("touchstart",e),m.addEventListener("click",e);for(var b,c=d(a),g=0,h=c.length;g<h;g++)b=c[g],b.addEventListener("touchstart",f),b.addEventListener("click",f)}}function d(a){var b,c=r.commentReplyClass;return a&&a.childNodes||(a=q),b=q.getElementsByClassName?a.getElementsByClassName(c):a.querySelectorAll("."+c)}function e(a){var b=this,c=r.temporaryFormId,d=j(c);d&&o&&(j(r.parentIdFieldId).value="0",d.parentNode.replaceChild(o,d),b.style.display="none",a.preventDefault())}function f(b){var c,d=this,e=i(d,"belowelement"),f=i(d,"commentid"),g=i(d,"respondelement"),h=i(d,"postid");e&&f&&g&&h&&(c=a.addComment.moveForm(e,f,g,h),!1===c&&b.preventDefault())}function g(){if(s){var a={childList:!0,subTree:!0};p=new s(h),p.observe(q.body,a)}}function h(a){for(var b=a.length;b--;)if(a[b].addedNodes.length)return void c()}function i(a,b){return u?a.dataset[b]:a.getAttribute("data-"+b)}function j(a){return q.getElementById(a)}function k(b,c,d,e){var f=j(b);o=j(d);var g,h,i,k=j(r.parentIdFieldId),p=j(r.postIdFieldId);if(f&&o&&k){l(o),e&&p&&(p.value=e),k.value=c,m.style.display="",f.parentNode.insertBefore(o,f.nextSibling),m.onclick=function(){return!1};try{for(var s=0;s<n.elements.length;s++)if(g=n.elements[s],h=!1,"getComputedStyle"in a?i=a.getComputedStyle(g):q.documentElement.currentStyle&&(i=g.currentStyle),(g.offsetWidth<=0&&g.offsetHeight<=0||"hidden"===i.visibility)&&(h=!0),"hidden"!==g.type&&!g.disabled&&!h){g.focus();break}}catch(t){}return!1}}function l(a){var b=r.temporaryFormId,c=j(b);c||(c=q.createElement("div"),c.id=b,c.style.display="none",a.parentNode.insertBefore(c,a))}var m,n,o,p,q=a.document,r={commentReplyClass:"comment-reply-link",cancelReplyId:"cancel-comment-reply-link",commentFormId:"commentform",temporaryFormId:"wp-temp-form-div",parentIdFieldId:"comment_parent",postIdFieldId:"comment_post_ID"},s=a.MutationObserver||a.WebKitMutationObserver||a.MozMutationObserver,t="querySelector"in q&&"addEventListener"in a,u=!!q.documentElement.dataset;return t&&"loading"!==q.readyState?b():t&&a.addEventListener("DOMContentLoaded",b,!1),{init:c,moveForm:k}}(window);
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: markjaquith, nacin
3
  Tags: hotfix, bugs, wordpress, update
4
  Requires at least: 3.0
5
- Tested up to: 3.5
6
- Stable tag: 1.1
7
 
8
  Provides unofficial fixes for selected WordPress bugs, so you don't have to wait for the next WordPress core release.
9
 
@@ -13,12 +13,8 @@ This unofficial plugin provides fixes for selected WordPress bugs, so you don't
13
 
14
  Recent fixes:
15
 
16
- * **WordPress 3.5**
17
- * Lets you update Twenty Twelve if it is shown as "broken" after installing 3.5
18
- * Lets you update WordPress and install or update plugins and themes. (Only some Windows servers running IIS were affected.) You'll need to [install this plugin manually](http://codex.wordpress.org/Managing_Plugins#Manual_Plugin_Installation).
19
-
20
- * **WordPress 3.4.2**
21
- * Fix adding and updating Custom Fields
22
 
23
  Fixes are specific to your version of WordPress. It may be that your version of WordPress has no fixes. That's fine. Keep the plugin activated and updated, in case you need it for a subsequent version of WordPress!
24
 
@@ -37,6 +33,9 @@ Or, you can do [a manual installation using FTP](http://codex.wordpress.org/Mana
37
  Read the "Complete Hotfix List" section in the description. A later version of the plugin may list the hotfixes in a special WordPress admin page.
38
 
39
  == Changelog ==
 
 
 
40
  = 1.1 =
41
  * Lets you update WordPress and install or update plugins and themes. (Only some Windows servers running IIS were affected.) You'll need to [install this plugin manually](http://codex.wordpress.org/Managing_Plugins#Manual_Plugin_Installation).
42
 
2
  Contributors: markjaquith, nacin
3
  Tags: hotfix, bugs, wordpress, update
4
  Requires at least: 3.0
5
+ Tested up to: 5.1
6
+ Stable tag: 1.2
7
 
8
  Provides unofficial fixes for selected WordPress bugs, so you don't have to wait for the next WordPress core release.
9
 
13
 
14
  Recent fixes:
15
 
16
+ * **WordPress 5.0**
17
+ * Lets themes enqueue `comment-reply.js` in the HTML header without JavaScript errors, see [WP#46280](https://core.trac.wordpress.org/ticket/46280).
 
 
 
 
18
 
19
  Fixes are specific to your version of WordPress. It may be that your version of WordPress has no fixes. That's fine. Keep the plugin activated and updated, in case you need it for a subsequent version of WordPress!
20
 
33
  Read the "Complete Hotfix List" section in the description. A later version of the plugin may list the hotfixes in a special WordPress admin page.
34
 
35
  == Changelog ==
36
+ = 1.2 =
37
+ * Lets themes enqueue `comment-reply.js` in the HTML header without JavaScript errors.
38
+
39
  = 1.1 =
40
  * Lets you update WordPress and install or update plugins and themes. (Only some Windows servers running IIS were affected.) You'll need to [install this plugin manually](http://codex.wordpress.org/Managing_Plugins#Manual_Plugin_Installation).
41