updates.js 79 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465
  1. /**
  2. * Functions for ajaxified updates, deletions and installs inside the WordPress admin.
  3. *
  4. * @version 4.2.0
  5. * @output wp-admin/js/updates.js
  6. */
  7. /* global pagenow */
  8. /**
  9. * @param {jQuery} $ jQuery object.
  10. * @param {object} wp WP object.
  11. * @param {object} settings WP Updates settings.
  12. * @param {string} settings.ajax_nonce AJAX nonce.
  13. * @param {object} settings.l10n Translation strings.
  14. * @param {object=} settings.plugins Base names of plugins in their different states.
  15. * @param {Array} settings.plugins.all Base names of all plugins.
  16. * @param {Array} settings.plugins.active Base names of active plugins.
  17. * @param {Array} settings.plugins.inactive Base names of inactive plugins.
  18. * @param {Array} settings.plugins.upgrade Base names of plugins with updates available.
  19. * @param {Array} settings.plugins.recently_activated Base names of recently activated plugins.
  20. * @param {object=} settings.themes Plugin/theme status information or null.
  21. * @param {number} settings.themes.all Amount of all themes.
  22. * @param {number} settings.themes.upgrade Amount of themes with updates available.
  23. * @param {number} settings.themes.disabled Amount of disabled themes.
  24. * @param {object=} settings.totals Combined information for available update counts.
  25. * @param {number} settings.totals.count Holds the amount of available updates.
  26. */
  27. (function( $, wp, settings ) {
  28. var $document = $( document );
  29. wp = wp || {};
  30. /**
  31. * The WP Updates object.
  32. *
  33. * @since 4.2.0
  34. *
  35. * @namespace wp.updates
  36. */
  37. wp.updates = {};
  38. /**
  39. * User nonce for ajax calls.
  40. *
  41. * @since 4.2.0
  42. *
  43. * @type {string}
  44. */
  45. wp.updates.ajaxNonce = settings.ajax_nonce;
  46. /**
  47. * Localized strings.
  48. *
  49. * @since 4.2.0
  50. *
  51. * @type {object}
  52. */
  53. wp.updates.l10n = settings.l10n;
  54. /**
  55. * Current search term.
  56. *
  57. * @since 4.6.0
  58. *
  59. * @type {string}
  60. */
  61. wp.updates.searchTerm = '';
  62. /**
  63. * Whether filesystem credentials need to be requested from the user.
  64. *
  65. * @since 4.2.0
  66. *
  67. * @type {bool}
  68. */
  69. wp.updates.shouldRequestFilesystemCredentials = false;
  70. /**
  71. * Filesystem credentials to be packaged along with the request.
  72. *
  73. * @since 4.2.0
  74. * @since 4.6.0 Added `available` property to indicate whether credentials have been provided.
  75. *
  76. * @type {Object}
  77. * @property {Object} filesystemCredentials.ftp Holds FTP credentials.
  78. * @property {string} filesystemCredentials.ftp.host FTP host. Default empty string.
  79. * @property {string} filesystemCredentials.ftp.username FTP user name. Default empty string.
  80. * @property {string} filesystemCredentials.ftp.password FTP password. Default empty string.
  81. * @property {string} filesystemCredentials.ftp.connectionType Type of FTP connection. 'ssh', 'ftp', or 'ftps'.
  82. * Default empty string.
  83. * @property {Object} filesystemCredentials.ssh Holds SSH credentials.
  84. * @property {string} filesystemCredentials.ssh.publicKey The public key. Default empty string.
  85. * @property {string} filesystemCredentials.ssh.privateKey The private key. Default empty string.
  86. * @property {string} filesystemCredentials.fsNonce Filesystem credentials form nonce.
  87. * @property {bool} filesystemCredentials.available Whether filesystem credentials have been provided.
  88. * Default 'false'.
  89. */
  90. wp.updates.filesystemCredentials = {
  91. ftp: {
  92. host: '',
  93. username: '',
  94. password: '',
  95. connectionType: ''
  96. },
  97. ssh: {
  98. publicKey: '',
  99. privateKey: ''
  100. },
  101. fsNonce: '',
  102. available: false
  103. };
  104. /**
  105. * Whether we're waiting for an Ajax request to complete.
  106. *
  107. * @since 4.2.0
  108. * @since 4.6.0 More accurately named `ajaxLocked`.
  109. *
  110. * @type {bool}
  111. */
  112. wp.updates.ajaxLocked = false;
  113. /**
  114. * Admin notice template.
  115. *
  116. * @since 4.6.0
  117. *
  118. * @type {function}
  119. */
  120. wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
  121. /**
  122. * Update queue.
  123. *
  124. * If the user tries to update a plugin while an update is
  125. * already happening, it can be placed in this queue to perform later.
  126. *
  127. * @since 4.2.0
  128. * @since 4.6.0 More accurately named `queue`.
  129. *
  130. * @type {Array.object}
  131. */
  132. wp.updates.queue = [];
  133. /**
  134. * Holds a jQuery reference to return focus to when exiting the request credentials modal.
  135. *
  136. * @since 4.2.0
  137. *
  138. * @type {jQuery}
  139. */
  140. wp.updates.$elToReturnFocusToFromCredentialsModal = undefined;
  141. /**
  142. * Adds or updates an admin notice.
  143. *
  144. * @since 4.6.0
  145. *
  146. * @param {object} data
  147. * @param {*=} data.selector Optional. Selector of an element to be replaced with the admin notice.
  148. * @param {string=} data.id Optional. Unique id that will be used as the notice's id attribute.
  149. * @param {string=} data.className Optional. Class names that will be used in the admin notice.
  150. * @param {string=} data.message Optional. The message displayed in the notice.
  151. * @param {number=} data.successes Optional. The amount of successful operations.
  152. * @param {number=} data.errors Optional. The amount of failed operations.
  153. * @param {Array=} data.errorMessages Optional. Error messages of failed operations.
  154. *
  155. */
  156. wp.updates.addAdminNotice = function( data ) {
  157. var $notice = $( data.selector ),
  158. $headerEnd = $( '.wp-header-end' ),
  159. $adminNotice;
  160. delete data.selector;
  161. $adminNotice = wp.updates.adminNotice( data );
  162. // Check if this admin notice already exists.
  163. if ( ! $notice.length ) {
  164. $notice = $( '#' + data.id );
  165. }
  166. if ( $notice.length ) {
  167. $notice.replaceWith( $adminNotice );
  168. } else if ( $headerEnd.length ) {
  169. $headerEnd.after( $adminNotice );
  170. } else {
  171. if ( 'customize' === pagenow ) {
  172. $( '.customize-themes-notifications' ).append( $adminNotice );
  173. } else {
  174. $( '.wrap' ).find( '> h1' ).after( $adminNotice );
  175. }
  176. }
  177. $document.trigger( 'wp-updates-notice-added' );
  178. };
  179. /**
  180. * Handles Ajax requests to WordPress.
  181. *
  182. * @since 4.6.0
  183. *
  184. * @param {string} action The type of Ajax request ('update-plugin', 'install-theme', etc).
  185. * @param {object} data Data that needs to be passed to the ajax callback.
  186. * @return {$.promise} A jQuery promise that represents the request,
  187. * decorated with an abort() method.
  188. */
  189. wp.updates.ajax = function( action, data ) {
  190. var options = {};
  191. if ( wp.updates.ajaxLocked ) {
  192. wp.updates.queue.push( {
  193. action: action,
  194. data: data
  195. } );
  196. // Return a Deferred object so callbacks can always be registered.
  197. return $.Deferred();
  198. }
  199. wp.updates.ajaxLocked = true;
  200. if ( data.success ) {
  201. options.success = data.success;
  202. delete data.success;
  203. }
  204. if ( data.error ) {
  205. options.error = data.error;
  206. delete data.error;
  207. }
  208. options.data = _.extend( data, {
  209. action: action,
  210. _ajax_nonce: wp.updates.ajaxNonce,
  211. _fs_nonce: wp.updates.filesystemCredentials.fsNonce,
  212. username: wp.updates.filesystemCredentials.ftp.username,
  213. password: wp.updates.filesystemCredentials.ftp.password,
  214. hostname: wp.updates.filesystemCredentials.ftp.hostname,
  215. connection_type: wp.updates.filesystemCredentials.ftp.connectionType,
  216. public_key: wp.updates.filesystemCredentials.ssh.publicKey,
  217. private_key: wp.updates.filesystemCredentials.ssh.privateKey
  218. } );
  219. return wp.ajax.send( options ).always( wp.updates.ajaxAlways );
  220. };
  221. /**
  222. * Actions performed after every Ajax request.
  223. *
  224. * @since 4.6.0
  225. *
  226. * @param {object} response
  227. * @param {array=} response.debug Optional. Debug information.
  228. * @param {string=} response.errorCode Optional. Error code for an error that occurred.
  229. */
  230. wp.updates.ajaxAlways = function( response ) {
  231. if ( ! response.errorCode || 'unable_to_connect_to_filesystem' !== response.errorCode ) {
  232. wp.updates.ajaxLocked = false;
  233. wp.updates.queueChecker();
  234. }
  235. if ( 'undefined' !== typeof response.debug && window.console && window.console.log ) {
  236. _.map( response.debug, function( message ) {
  237. // Remove all HTML tags and write a message to the console.
  238. window.console.log( wp.sanitize.stripTagsAndEncodeText( message ) );
  239. } );
  240. }
  241. };
  242. /**
  243. * Refreshes update counts everywhere on the screen.
  244. *
  245. * @since 4.7.0
  246. */
  247. wp.updates.refreshCount = function() {
  248. var $adminBarUpdates = $( '#wp-admin-bar-updates' ),
  249. $dashboardNavMenuUpdateCount = $( 'a[href="update-core.php"] .update-plugins' ),
  250. $pluginsNavMenuUpdateCount = $( 'a[href="plugins.php"] .update-plugins' ),
  251. $appearanceNavMenuUpdateCount = $( 'a[href="themes.php"] .update-plugins' ),
  252. itemCount;
  253. $adminBarUpdates.find( '.ab-item' ).removeAttr( 'title' );
  254. $adminBarUpdates.find( '.ab-label' ).text( settings.totals.counts.total );
  255. // Remove the update count from the toolbar if it's zero.
  256. if ( 0 === settings.totals.counts.total ) {
  257. $adminBarUpdates.find( '.ab-label' ).parents( 'li' ).remove();
  258. }
  259. // Update the "Updates" menu item.
  260. $dashboardNavMenuUpdateCount.each( function( index, element ) {
  261. element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.total );
  262. } );
  263. if ( settings.totals.counts.total > 0 ) {
  264. $dashboardNavMenuUpdateCount.find( '.update-count' ).text( settings.totals.counts.total );
  265. } else {
  266. $dashboardNavMenuUpdateCount.remove();
  267. }
  268. // Update the "Plugins" menu item.
  269. $pluginsNavMenuUpdateCount.each( function( index, element ) {
  270. element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.plugins );
  271. } );
  272. if ( settings.totals.counts.total > 0 ) {
  273. $pluginsNavMenuUpdateCount.find( '.plugin-count' ).text( settings.totals.counts.plugins );
  274. } else {
  275. $pluginsNavMenuUpdateCount.remove();
  276. }
  277. // Update the "Appearance" menu item.
  278. $appearanceNavMenuUpdateCount.each( function( index, element ) {
  279. element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.themes );
  280. } );
  281. if ( settings.totals.counts.total > 0 ) {
  282. $appearanceNavMenuUpdateCount.find( '.theme-count' ).text( settings.totals.counts.themes );
  283. } else {
  284. $appearanceNavMenuUpdateCount.remove();
  285. }
  286. // Update list table filter navigation.
  287. if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
  288. itemCount = settings.totals.counts.plugins;
  289. } else if ( 'themes' === pagenow || 'themes-network' === pagenow ) {
  290. itemCount = settings.totals.counts.themes;
  291. }
  292. if ( itemCount > 0 ) {
  293. $( '.subsubsub .upgrade .count' ).text( '(' + itemCount + ')' );
  294. } else {
  295. $( '.subsubsub .upgrade' ).remove();
  296. $( '.subsubsub li:last' ).html( function() { return $( this ).children(); } );
  297. }
  298. };
  299. /**
  300. * Decrements the update counts throughout the various menus.
  301. *
  302. * This includes the toolbar, the "Updates" menu item and the menu items
  303. * for plugins and themes.
  304. *
  305. * @since 3.9.0
  306. *
  307. * @param {string} type The type of item that was updated or deleted.
  308. * Can be 'plugin', 'theme'.
  309. */
  310. wp.updates.decrementCount = function( type ) {
  311. settings.totals.counts.total = Math.max( --settings.totals.counts.total, 0 );
  312. if ( 'plugin' === type ) {
  313. settings.totals.counts.plugins = Math.max( --settings.totals.counts.plugins, 0 );
  314. } else if ( 'theme' === type ) {
  315. settings.totals.counts.themes = Math.max( --settings.totals.counts.themes, 0 );
  316. }
  317. wp.updates.refreshCount( type );
  318. };
  319. /**
  320. * Sends an Ajax request to the server to update a plugin.
  321. *
  322. * @since 4.2.0
  323. * @since 4.6.0 More accurately named `updatePlugin`.
  324. *
  325. * @param {object} args Arguments.
  326. * @param {string} args.plugin Plugin basename.
  327. * @param {string} args.slug Plugin slug.
  328. * @param {updatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.updatePluginSuccess
  329. * @param {updatePluginError=} args.error Optional. Error callback. Default: wp.updates.updatePluginError
  330. * @return {$.promise} A jQuery promise that represents the request,
  331. * decorated with an abort() method.
  332. */
  333. wp.updates.updatePlugin = function( args ) {
  334. var $updateRow, $card, $message, message;
  335. args = _.extend( {
  336. success: wp.updates.updatePluginSuccess,
  337. error: wp.updates.updatePluginError
  338. }, args );
  339. if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
  340. $updateRow = $( 'tr[data-plugin="' + args.plugin + '"]' );
  341. $message = $updateRow.find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
  342. message = wp.updates.l10n.pluginUpdatingLabel.replace( '%s', $updateRow.find( '.plugin-title strong' ).text() );
  343. } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
  344. $card = $( '.plugin-card-' + args.slug );
  345. $message = $card.find( '.update-now' ).addClass( 'updating-message' );
  346. message = wp.updates.l10n.pluginUpdatingLabel.replace( '%s', $message.data( 'name' ) );
  347. // Remove previous error messages, if any.
  348. $card.removeClass( 'plugin-card-update-failed' ).find( '.notice.notice-error' ).remove();
  349. }
  350. if ( $message.html() !== wp.updates.l10n.updating ) {
  351. $message.data( 'originaltext', $message.html() );
  352. }
  353. $message
  354. .attr( 'aria-label', message )
  355. .text( wp.updates.l10n.updating );
  356. $document.trigger( 'wp-plugin-updating', args );
  357. return wp.updates.ajax( 'update-plugin', args );
  358. };
  359. /**
  360. * Updates the UI appropriately after a successful plugin update.
  361. *
  362. * @since 4.2.0
  363. * @since 4.6.0 More accurately named `updatePluginSuccess`.
  364. *
  365. * @param {object} response Response from the server.
  366. * @param {string} response.slug Slug of the plugin to be updated.
  367. * @param {string} response.plugin Basename of the plugin to be updated.
  368. * @param {string} response.pluginName Name of the plugin to be updated.
  369. * @param {string} response.oldVersion Old version of the plugin.
  370. * @param {string} response.newVersion New version of the plugin.
  371. */
  372. wp.updates.updatePluginSuccess = function( response ) {
  373. var $pluginRow, $updateMessage, newText;
  374. if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
  375. $pluginRow = $( 'tr[data-plugin="' + response.plugin + '"]' )
  376. .removeClass( 'update' )
  377. .addClass( 'updated' );
  378. $updateMessage = $pluginRow.find( '.update-message' )
  379. .removeClass( 'updating-message notice-warning' )
  380. .addClass( 'updated-message notice-success' ).find( 'p' );
  381. // Update the version number in the row.
  382. newText = $pluginRow.find( '.plugin-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
  383. $pluginRow.find( '.plugin-version-author-uri' ).html( newText );
  384. } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
  385. $updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' )
  386. .removeClass( 'updating-message' )
  387. .addClass( 'button-disabled updated-message' );
  388. }
  389. $updateMessage
  390. .attr( 'aria-label', wp.updates.l10n.pluginUpdatedLabel.replace( '%s', response.pluginName ) )
  391. .text( wp.updates.l10n.pluginUpdated );
  392. wp.a11y.speak( wp.updates.l10n.updatedMsg, 'polite' );
  393. wp.updates.decrementCount( 'plugin' );
  394. $document.trigger( 'wp-plugin-update-success', response );
  395. };
  396. /**
  397. * Updates the UI appropriately after a failed plugin update.
  398. *
  399. * @since 4.2.0
  400. * @since 4.6.0 More accurately named `updatePluginError`.
  401. *
  402. * @param {object} response Response from the server.
  403. * @param {string} response.slug Slug of the plugin to be updated.
  404. * @param {string} response.plugin Basename of the plugin to be updated.
  405. * @param {string=} response.pluginName Optional. Name of the plugin to be updated.
  406. * @param {string} response.errorCode Error code for the error that occurred.
  407. * @param {string} response.errorMessage The error that occurred.
  408. */
  409. wp.updates.updatePluginError = function( response ) {
  410. var $card, $message, errorMessage;
  411. if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
  412. return;
  413. }
  414. if ( wp.updates.maybeHandleCredentialError( response, 'update-plugin' ) ) {
  415. return;
  416. }
  417. errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage );
  418. if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
  419. if ( response.plugin ) {
  420. $message = $( 'tr[data-plugin="' + response.plugin + '"]' ).find( '.update-message' );
  421. } else {
  422. $message = $( 'tr[data-slug="' + response.slug + '"]' ).find( '.update-message' );
  423. }
  424. $message.removeClass( 'updating-message notice-warning' ).addClass( 'notice-error' ).find( 'p' ).html( errorMessage );
  425. if ( response.pluginName ) {
  426. $message.find( 'p' )
  427. .attr( 'aria-label', wp.updates.l10n.pluginUpdateFailedLabel.replace( '%s', response.pluginName ) );
  428. } else {
  429. $message.find( 'p' ).removeAttr( 'aria-label' );
  430. }
  431. } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
  432. $card = $( '.plugin-card-' + response.slug )
  433. .addClass( 'plugin-card-update-failed' )
  434. .append( wp.updates.adminNotice( {
  435. className: 'update-message notice-error notice-alt is-dismissible',
  436. message: errorMessage
  437. } ) );
  438. $card.find( '.update-now' )
  439. .text( wp.updates.l10n.updateFailedShort ).removeClass( 'updating-message' );
  440. if ( response.pluginName ) {
  441. $card.find( '.update-now' )
  442. .attr( 'aria-label', wp.updates.l10n.pluginUpdateFailedLabel.replace( '%s', response.pluginName ) );
  443. } else {
  444. $card.find( '.update-now' ).removeAttr( 'aria-label' );
  445. }
  446. $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
  447. // Use same delay as the total duration of the notice fadeTo + slideUp animation.
  448. setTimeout( function() {
  449. $card
  450. .removeClass( 'plugin-card-update-failed' )
  451. .find( '.column-name a' ).focus();
  452. $card.find( '.update-now' )
  453. .attr( 'aria-label', false )
  454. .text( wp.updates.l10n.updateNow );
  455. }, 200 );
  456. } );
  457. }
  458. wp.a11y.speak( errorMessage, 'assertive' );
  459. $document.trigger( 'wp-plugin-update-error', response );
  460. };
  461. /**
  462. * Sends an Ajax request to the server to install a plugin.
  463. *
  464. * @since 4.6.0
  465. *
  466. * @param {object} args Arguments.
  467. * @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository.
  468. * @param {installPluginSuccess=} args.success Optional. Success callback. Default: wp.updates.installPluginSuccess
  469. * @param {installPluginError=} args.error Optional. Error callback. Default: wp.updates.installPluginError
  470. * @return {$.promise} A jQuery promise that represents the request,
  471. * decorated with an abort() method.
  472. */
  473. wp.updates.installPlugin = function( args ) {
  474. var $card = $( '.plugin-card-' + args.slug ),
  475. $message = $card.find( '.install-now' );
  476. args = _.extend( {
  477. success: wp.updates.installPluginSuccess,
  478. error: wp.updates.installPluginError
  479. }, args );
  480. if ( 'import' === pagenow ) {
  481. $message = $( '[data-slug="' + args.slug + '"]' );
  482. }
  483. if ( $message.html() !== wp.updates.l10n.installing ) {
  484. $message.data( 'originaltext', $message.html() );
  485. }
  486. $message
  487. .addClass( 'updating-message' )
  488. .attr( 'aria-label', wp.updates.l10n.pluginInstallingLabel.replace( '%s', $message.data( 'name' ) ) )
  489. .text( wp.updates.l10n.installing );
  490. wp.a11y.speak( wp.updates.l10n.installingMsg, 'polite' );
  491. // Remove previous error messages, if any.
  492. $card.removeClass( 'plugin-card-install-failed' ).find( '.notice.notice-error' ).remove();
  493. $document.trigger( 'wp-plugin-installing', args );
  494. return wp.updates.ajax( 'install-plugin', args );
  495. };
  496. /**
  497. * Updates the UI appropriately after a successful plugin install.
  498. *
  499. * @since 4.6.0
  500. *
  501. * @param {object} response Response from the server.
  502. * @param {string} response.slug Slug of the installed plugin.
  503. * @param {string} response.pluginName Name of the installed plugin.
  504. * @param {string} response.activateUrl URL to activate the just installed plugin.
  505. */
  506. wp.updates.installPluginSuccess = function( response ) {
  507. var $message = $( '.plugin-card-' + response.slug ).find( '.install-now' );
  508. $message
  509. .removeClass( 'updating-message' )
  510. .addClass( 'updated-message installed button-disabled' )
  511. .attr( 'aria-label', wp.updates.l10n.pluginInstalledLabel.replace( '%s', response.pluginName ) )
  512. .text( wp.updates.l10n.pluginInstalled );
  513. wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
  514. $document.trigger( 'wp-plugin-install-success', response );
  515. if ( response.activateUrl ) {
  516. setTimeout( function() {
  517. // Transform the 'Install' button into an 'Activate' button.
  518. $message.removeClass( 'install-now installed button-disabled updated-message' ).addClass( 'activate-now button-primary' )
  519. .attr( 'href', response.activateUrl )
  520. .attr( 'aria-label', wp.updates.l10n.activatePluginLabel.replace( '%s', response.pluginName ) )
  521. .text( wp.updates.l10n.activatePlugin );
  522. }, 1000 );
  523. }
  524. };
  525. /**
  526. * Updates the UI appropriately after a failed plugin install.
  527. *
  528. * @since 4.6.0
  529. *
  530. * @param {object} response Response from the server.
  531. * @param {string} response.slug Slug of the plugin to be installed.
  532. * @param {string=} response.pluginName Optional. Name of the plugin to be installed.
  533. * @param {string} response.errorCode Error code for the error that occurred.
  534. * @param {string} response.errorMessage The error that occurred.
  535. */
  536. wp.updates.installPluginError = function( response ) {
  537. var $card = $( '.plugin-card-' + response.slug ),
  538. $button = $card.find( '.install-now' ),
  539. errorMessage;
  540. if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
  541. return;
  542. }
  543. if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) {
  544. return;
  545. }
  546. errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage );
  547. $card
  548. .addClass( 'plugin-card-update-failed' )
  549. .append( '<div class="notice notice-error notice-alt is-dismissible"><p>' + errorMessage + '</p></div>' );
  550. $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
  551. // Use same delay as the total duration of the notice fadeTo + slideUp animation.
  552. setTimeout( function() {
  553. $card
  554. .removeClass( 'plugin-card-update-failed' )
  555. .find( '.column-name a' ).focus();
  556. }, 200 );
  557. } );
  558. $button
  559. .removeClass( 'updating-message' ).addClass( 'button-disabled' )
  560. .attr( 'aria-label', wp.updates.l10n.pluginInstallFailedLabel.replace( '%s', $button.data( 'name' ) ) )
  561. .text( wp.updates.l10n.installFailedShort );
  562. wp.a11y.speak( errorMessage, 'assertive' );
  563. $document.trigger( 'wp-plugin-install-error', response );
  564. };
  565. /**
  566. * Updates the UI appropriately after a successful importer install.
  567. *
  568. * @since 4.6.0
  569. *
  570. * @param {object} response Response from the server.
  571. * @param {string} response.slug Slug of the installed plugin.
  572. * @param {string} response.pluginName Name of the installed plugin.
  573. * @param {string} response.activateUrl URL to activate the just installed plugin.
  574. */
  575. wp.updates.installImporterSuccess = function( response ) {
  576. wp.updates.addAdminNotice( {
  577. id: 'install-success',
  578. className: 'notice-success is-dismissible',
  579. message: wp.updates.l10n.importerInstalledMsg.replace( '%s', response.activateUrl + '&from=import' )
  580. } );
  581. $( '[data-slug="' + response.slug + '"]' )
  582. .removeClass( 'install-now updating-message' )
  583. .addClass( 'activate-now' )
  584. .attr({
  585. 'href': response.activateUrl + '&from=import',
  586. 'aria-label': wp.updates.l10n.activateImporterLabel.replace( '%s', response.pluginName )
  587. })
  588. .text( wp.updates.l10n.activateImporter );
  589. wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
  590. $document.trigger( 'wp-importer-install-success', response );
  591. };
  592. /**
  593. * Updates the UI appropriately after a failed importer install.
  594. *
  595. * @since 4.6.0
  596. *
  597. * @param {object} response Response from the server.
  598. * @param {string} response.slug Slug of the plugin to be installed.
  599. * @param {string=} response.pluginName Optional. Name of the plugin to be installed.
  600. * @param {string} response.errorCode Error code for the error that occurred.
  601. * @param {string} response.errorMessage The error that occurred.
  602. */
  603. wp.updates.installImporterError = function( response ) {
  604. var errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage ),
  605. $installLink = $( '[data-slug="' + response.slug + '"]' ),
  606. pluginName = $installLink.data( 'name' );
  607. if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
  608. return;
  609. }
  610. if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) {
  611. return;
  612. }
  613. wp.updates.addAdminNotice( {
  614. id: response.errorCode,
  615. className: 'notice-error is-dismissible',
  616. message: errorMessage
  617. } );
  618. $installLink
  619. .removeClass( 'updating-message' )
  620. .text( wp.updates.l10n.installNow )
  621. .attr( 'aria-label', wp.updates.l10n.pluginInstallNowLabel.replace( '%s', pluginName ) );
  622. wp.a11y.speak( errorMessage, 'assertive' );
  623. $document.trigger( 'wp-importer-install-error', response );
  624. };
  625. /**
  626. * Sends an Ajax request to the server to delete a plugin.
  627. *
  628. * @since 4.6.0
  629. *
  630. * @param {object} args Arguments.
  631. * @param {string} args.plugin Basename of the plugin to be deleted.
  632. * @param {string} args.slug Slug of the plugin to be deleted.
  633. * @param {deletePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.deletePluginSuccess
  634. * @param {deletePluginError=} args.error Optional. Error callback. Default: wp.updates.deletePluginError
  635. * @return {$.promise} A jQuery promise that represents the request,
  636. * decorated with an abort() method.
  637. */
  638. wp.updates.deletePlugin = function( args ) {
  639. var $link = $( '[data-plugin="' + args.plugin + '"]' ).find( '.row-actions a.delete' );
  640. args = _.extend( {
  641. success: wp.updates.deletePluginSuccess,
  642. error: wp.updates.deletePluginError
  643. }, args );
  644. if ( $link.html() !== wp.updates.l10n.deleting ) {
  645. $link
  646. .data( 'originaltext', $link.html() )
  647. .text( wp.updates.l10n.deleting );
  648. }
  649. wp.a11y.speak( wp.updates.l10n.deleting, 'polite' );
  650. $document.trigger( 'wp-plugin-deleting', args );
  651. return wp.updates.ajax( 'delete-plugin', args );
  652. };
  653. /**
  654. * Updates the UI appropriately after a successful plugin deletion.
  655. *
  656. * @since 4.6.0
  657. *
  658. * @param {Object} response Response from the server.
  659. * @param {string} response.slug Slug of the plugin that was deleted.
  660. * @param {string} response.plugin Base name of the plugin that was deleted.
  661. * @param {string} response.pluginName Name of the plugin that was deleted.
  662. */
  663. wp.updates.deletePluginSuccess = function( response ) {
  664. // Removes the plugin and updates rows.
  665. $( '[data-plugin="' + response.plugin + '"]' ).css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
  666. var $form = $( '#bulk-action-form' ),
  667. $views = $( '.subsubsub' ),
  668. $pluginRow = $( this ),
  669. columnCount = $form.find( 'thead th:not(.hidden), thead td' ).length,
  670. pluginDeletedRow = wp.template( 'item-deleted-row' ),
  671. /**
  672. * Plugins Base names of plugins in their different states.
  673. *
  674. * @type {Object}
  675. */
  676. plugins = settings.plugins;
  677. // Add a success message after deleting a plugin.
  678. if ( ! $pluginRow.hasClass( 'plugin-update-tr' ) ) {
  679. $pluginRow.after(
  680. pluginDeletedRow( {
  681. slug: response.slug,
  682. plugin: response.plugin,
  683. colspan: columnCount,
  684. name: response.pluginName
  685. } )
  686. );
  687. }
  688. $pluginRow.remove();
  689. // Remove plugin from update count.
  690. if ( -1 !== _.indexOf( plugins.upgrade, response.plugin ) ) {
  691. plugins.upgrade = _.without( plugins.upgrade, response.plugin );
  692. wp.updates.decrementCount( 'plugin' );
  693. }
  694. // Remove from views.
  695. if ( -1 !== _.indexOf( plugins.inactive, response.plugin ) ) {
  696. plugins.inactive = _.without( plugins.inactive, response.plugin );
  697. if ( plugins.inactive.length ) {
  698. $views.find( '.inactive .count' ).text( '(' + plugins.inactive.length + ')' );
  699. } else {
  700. $views.find( '.inactive' ).remove();
  701. }
  702. }
  703. if ( -1 !== _.indexOf( plugins.active, response.plugin ) ) {
  704. plugins.active = _.without( plugins.active, response.plugin );
  705. if ( plugins.active.length ) {
  706. $views.find( '.active .count' ).text( '(' + plugins.active.length + ')' );
  707. } else {
  708. $views.find( '.active' ).remove();
  709. }
  710. }
  711. if ( -1 !== _.indexOf( plugins.recently_activated, response.plugin ) ) {
  712. plugins.recently_activated = _.without( plugins.recently_activated, response.plugin );
  713. if ( plugins.recently_activated.length ) {
  714. $views.find( '.recently_activated .count' ).text( '(' + plugins.recently_activated.length + ')' );
  715. } else {
  716. $views.find( '.recently_activated' ).remove();
  717. }
  718. }
  719. plugins.all = _.without( plugins.all, response.plugin );
  720. if ( plugins.all.length ) {
  721. $views.find( '.all .count' ).text( '(' + plugins.all.length + ')' );
  722. } else {
  723. $form.find( '.tablenav' ).css( { visibility: 'hidden' } );
  724. $views.find( '.all' ).remove();
  725. if ( ! $form.find( 'tr.no-items' ).length ) {
  726. $form.find( '#the-list' ).append( '<tr class="no-items"><td class="colspanchange" colspan="' + columnCount + '">' + wp.updates.l10n.noPlugins + '</td></tr>' );
  727. }
  728. }
  729. } );
  730. wp.a11y.speak( wp.updates.l10n.pluginDeleted, 'polite' );
  731. $document.trigger( 'wp-plugin-delete-success', response );
  732. };
  733. /**
  734. * Updates the UI appropriately after a failed plugin deletion.
  735. *
  736. * @since 4.6.0
  737. *
  738. * @param {object} response Response from the server.
  739. * @param {string} response.slug Slug of the plugin to be deleted.
  740. * @param {string} response.plugin Base name of the plugin to be deleted
  741. * @param {string=} response.pluginName Optional. Name of the plugin to be deleted.
  742. * @param {string} response.errorCode Error code for the error that occurred.
  743. * @param {string} response.errorMessage The error that occurred.
  744. */
  745. wp.updates.deletePluginError = function( response ) {
  746. var $plugin, $pluginUpdateRow,
  747. pluginUpdateRow = wp.template( 'item-update-row' ),
  748. noticeContent = wp.updates.adminNotice( {
  749. className: 'update-message notice-error notice-alt',
  750. message: response.errorMessage
  751. } );
  752. if ( response.plugin ) {
  753. $plugin = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' );
  754. $pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' );
  755. } else {
  756. $plugin = $( 'tr.inactive[data-slug="' + response.slug + '"]' );
  757. $pluginUpdateRow = $plugin.siblings( '[data-slug="' + response.slug + '"]' );
  758. }
  759. if ( ! wp.updates.isValidResponse( response, 'delete' ) ) {
  760. return;
  761. }
  762. if ( wp.updates.maybeHandleCredentialError( response, 'delete-plugin' ) ) {
  763. return;
  764. }
  765. // Add a plugin update row if it doesn't exist yet.
  766. if ( ! $pluginUpdateRow.length ) {
  767. $plugin.addClass( 'update' ).after(
  768. pluginUpdateRow( {
  769. slug: response.slug,
  770. plugin: response.plugin || response.slug,
  771. colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
  772. content: noticeContent
  773. } )
  774. );
  775. } else {
  776. // Remove previous error messages, if any.
  777. $pluginUpdateRow.find( '.notice-error' ).remove();
  778. $pluginUpdateRow.find( '.plugin-update' ).append( noticeContent );
  779. }
  780. $document.trigger( 'wp-plugin-delete-error', response );
  781. };
  782. /**
  783. * Sends an Ajax request to the server to update a theme.
  784. *
  785. * @since 4.6.0
  786. *
  787. * @param {object} args Arguments.
  788. * @param {string} args.slug Theme stylesheet.
  789. * @param {updateThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.updateThemeSuccess
  790. * @param {updateThemeError=} args.error Optional. Error callback. Default: wp.updates.updateThemeError
  791. * @return {$.promise} A jQuery promise that represents the request,
  792. * decorated with an abort() method.
  793. */
  794. wp.updates.updateTheme = function( args ) {
  795. var $notice;
  796. args = _.extend( {
  797. success: wp.updates.updateThemeSuccess,
  798. error: wp.updates.updateThemeError
  799. }, args );
  800. if ( 'themes-network' === pagenow ) {
  801. $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
  802. } else if ( 'customize' === pagenow ) {
  803. // Update the theme details UI.
  804. $notice = $( '[data-slug="' + args.slug + '"].notice' ).removeClass( 'notice-large' );
  805. $notice.find( 'h3' ).remove();
  806. // Add the top-level UI, and update both.
  807. $notice = $notice.add( $( '#customize-control-installed_theme_' + args.slug ).find( '.update-message' ) );
  808. $notice = $notice.addClass( 'updating-message' ).find( 'p' );
  809. } else {
  810. $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' );
  811. $notice.find( 'h3' ).remove();
  812. $notice = $notice.add( $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ) );
  813. $notice = $notice.addClass( 'updating-message' ).find( 'p' );
  814. }
  815. if ( $notice.html() !== wp.updates.l10n.updating ) {
  816. $notice.data( 'originaltext', $notice.html() );
  817. }
  818. wp.a11y.speak( wp.updates.l10n.updatingMsg, 'polite' );
  819. $notice.text( wp.updates.l10n.updating );
  820. $document.trigger( 'wp-theme-updating', args );
  821. return wp.updates.ajax( 'update-theme', args );
  822. };
  823. /**
  824. * Updates the UI appropriately after a successful theme update.
  825. *
  826. * @since 4.6.0
  827. *
  828. * @param {object} response
  829. * @param {string} response.slug Slug of the theme to be updated.
  830. * @param {object} response.theme Updated theme.
  831. * @param {string} response.oldVersion Old version of the theme.
  832. * @param {string} response.newVersion New version of the theme.
  833. */
  834. wp.updates.updateThemeSuccess = function( response ) {
  835. var isModalOpen = $( 'body.modal-open' ).length,
  836. $theme = $( '[data-slug="' + response.slug + '"]' ),
  837. updatedMessage = {
  838. className: 'updated-message notice-success notice-alt',
  839. message: wp.updates.l10n.themeUpdated
  840. },
  841. $notice, newText;
  842. if ( 'customize' === pagenow ) {
  843. $theme = $( '.updating-message' ).siblings( '.theme-name' );
  844. if ( $theme.length ) {
  845. // Update the version number in the row.
  846. newText = $theme.html().replace( response.oldVersion, response.newVersion );
  847. $theme.html( newText );
  848. }
  849. $notice = $( '.theme-info .notice' ).add( wp.customize.control( 'installed_theme_' + response.slug ).container.find( '.theme' ).find( '.update-message' ) );
  850. } else if ( 'themes-network' === pagenow ) {
  851. $notice = $theme.find( '.update-message' );
  852. // Update the version number in the row.
  853. newText = $theme.find( '.theme-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
  854. $theme.find( '.theme-version-author-uri' ).html( newText );
  855. } else {
  856. $notice = $( '.theme-info .notice' ).add( $theme.find( '.update-message' ) );
  857. // Focus on Customize button after updating.
  858. if ( isModalOpen ) {
  859. $( '.load-customize:visible' ).focus();
  860. } else {
  861. $theme.find( '.load-customize' ).focus();
  862. }
  863. }
  864. wp.updates.addAdminNotice( _.extend( { selector: $notice }, updatedMessage ) );
  865. wp.a11y.speak( wp.updates.l10n.updatedMsg, 'polite' );
  866. wp.updates.decrementCount( 'theme' );
  867. $document.trigger( 'wp-theme-update-success', response );
  868. // Show updated message after modal re-rendered.
  869. if ( isModalOpen && 'customize' !== pagenow ) {
  870. $( '.theme-info .theme-author' ).after( wp.updates.adminNotice( updatedMessage ) );
  871. }
  872. };
  873. /**
  874. * Updates the UI appropriately after a failed theme update.
  875. *
  876. * @since 4.6.0
  877. *
  878. * @param {object} response Response from the server.
  879. * @param {string} response.slug Slug of the theme to be updated.
  880. * @param {string} response.errorCode Error code for the error that occurred.
  881. * @param {string} response.errorMessage The error that occurred.
  882. */
  883. wp.updates.updateThemeError = function( response ) {
  884. var $theme = $( '[data-slug="' + response.slug + '"]' ),
  885. errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage ),
  886. $notice;
  887. if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
  888. return;
  889. }
  890. if ( wp.updates.maybeHandleCredentialError( response, 'update-theme' ) ) {
  891. return;
  892. }
  893. if ( 'customize' === pagenow ) {
  894. $theme = wp.customize.control( 'installed_theme_' + response.slug ).container.find( '.theme' );
  895. }
  896. if ( 'themes-network' === pagenow ) {
  897. $notice = $theme.find( '.update-message ' );
  898. } else {
  899. $notice = $( '.theme-info .notice' ).add( $theme.find( '.notice' ) );
  900. $( 'body.modal-open' ).length ? $( '.load-customize:visible' ).focus() : $theme.find( '.load-customize' ).focus();
  901. }
  902. wp.updates.addAdminNotice( {
  903. selector: $notice,
  904. className: 'update-message notice-error notice-alt is-dismissible',
  905. message: errorMessage
  906. } );
  907. wp.a11y.speak( errorMessage, 'polite' );
  908. $document.trigger( 'wp-theme-update-error', response );
  909. };
  910. /**
  911. * Sends an Ajax request to the server to install a theme.
  912. *
  913. * @since 4.6.0
  914. *
  915. * @param {object} args
  916. * @param {string} args.slug Theme stylesheet.
  917. * @param {installThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.installThemeSuccess
  918. * @param {installThemeError=} args.error Optional. Error callback. Default: wp.updates.installThemeError
  919. * @return {$.promise} A jQuery promise that represents the request,
  920. * decorated with an abort() method.
  921. */
  922. wp.updates.installTheme = function( args ) {
  923. var $message = $( '.theme-install[data-slug="' + args.slug + '"]' );
  924. args = _.extend( {
  925. success: wp.updates.installThemeSuccess,
  926. error: wp.updates.installThemeError
  927. }, args );
  928. $message.addClass( 'updating-message' );
  929. $message.parents( '.theme' ).addClass( 'focus' );
  930. if ( $message.html() !== wp.updates.l10n.installing ) {
  931. $message.data( 'originaltext', $message.html() );
  932. }
  933. $message
  934. .text( wp.updates.l10n.installing )
  935. .attr( 'aria-label', wp.updates.l10n.themeInstallingLabel.replace( '%s', $message.data( 'name' ) ) );
  936. wp.a11y.speak( wp.updates.l10n.installingMsg, 'polite' );
  937. // Remove previous error messages, if any.
  938. $( '.install-theme-info, [data-slug="' + args.slug + '"]' ).removeClass( 'theme-install-failed' ).find( '.notice.notice-error' ).remove();
  939. $document.trigger( 'wp-theme-installing', args );
  940. return wp.updates.ajax( 'install-theme', args );
  941. };
  942. /**
  943. * Updates the UI appropriately after a successful theme install.
  944. *
  945. * @since 4.6.0
  946. *
  947. * @param {object} response Response from the server.
  948. * @param {string} response.slug Slug of the theme to be installed.
  949. * @param {string} response.customizeUrl URL to the Customizer for the just installed theme.
  950. * @param {string} response.activateUrl URL to activate the just installed theme.
  951. */
  952. wp.updates.installThemeSuccess = function( response ) {
  953. var $card = $( '.wp-full-overlay-header, [data-slug=' + response.slug + ']' ),
  954. $message;
  955. $document.trigger( 'wp-theme-install-success', response );
  956. $message = $card.find( '.button-primary' )
  957. .removeClass( 'updating-message' )
  958. .addClass( 'updated-message disabled' )
  959. .attr( 'aria-label', wp.updates.l10n.themeInstalledLabel.replace( '%s', response.themeName ) )
  960. .text( wp.updates.l10n.themeInstalled );
  961. wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
  962. setTimeout( function() {
  963. if ( response.activateUrl ) {
  964. // Transform the 'Install' button into an 'Activate' button.
  965. $message
  966. .attr( 'href', response.activateUrl )
  967. .removeClass( 'theme-install updated-message disabled' )
  968. .addClass( 'activate' )
  969. .attr( 'aria-label', wp.updates.l10n.activateThemeLabel.replace( '%s', response.themeName ) )
  970. .text( wp.updates.l10n.activateTheme );
  971. }
  972. if ( response.customizeUrl ) {
  973. // Transform the 'Preview' button into a 'Live Preview' button.
  974. $message.siblings( '.preview' ).replaceWith( function () {
  975. return $( '<a>' )
  976. .attr( 'href', response.customizeUrl )
  977. .addClass( 'button load-customize' )
  978. .text( wp.updates.l10n.livePreview );
  979. } );
  980. }
  981. }, 1000 );
  982. };
  983. /**
  984. * Updates the UI appropriately after a failed theme install.
  985. *
  986. * @since 4.6.0
  987. *
  988. * @param {object} response Response from the server.
  989. * @param {string} response.slug Slug of the theme to be installed.
  990. * @param {string} response.errorCode Error code for the error that occurred.
  991. * @param {string} response.errorMessage The error that occurred.
  992. */
  993. wp.updates.installThemeError = function( response ) {
  994. var $card, $button,
  995. errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage ),
  996. $message = wp.updates.adminNotice( {
  997. className: 'update-message notice-error notice-alt',
  998. message: errorMessage
  999. } );
  1000. if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
  1001. return;
  1002. }
  1003. if ( wp.updates.maybeHandleCredentialError( response, 'install-theme' ) ) {
  1004. return;
  1005. }
  1006. if ( 'customize' === pagenow ) {
  1007. if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) {
  1008. $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
  1009. $card = $( '.theme-overlay .theme-info' ).prepend( $message );
  1010. } else {
  1011. $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
  1012. $card = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message );
  1013. }
  1014. wp.customize.notifications.remove( 'theme_installing' );
  1015. } else {
  1016. if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) {
  1017. $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
  1018. $card = $( '.install-theme-info' ).prepend( $message );
  1019. } else {
  1020. $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message );
  1021. $button = $card.find( '.theme-install' );
  1022. }
  1023. }
  1024. $button
  1025. .removeClass( 'updating-message' )
  1026. .attr( 'aria-label', wp.updates.l10n.themeInstallFailedLabel.replace( '%s', $button.data( 'name' ) ) )
  1027. .text( wp.updates.l10n.installFailedShort );
  1028. wp.a11y.speak( errorMessage, 'assertive' );
  1029. $document.trigger( 'wp-theme-install-error', response );
  1030. };
  1031. /**
  1032. * Sends an Ajax request to the server to delete a theme.
  1033. *
  1034. * @since 4.6.0
  1035. *
  1036. * @param {object} args
  1037. * @param {string} args.slug Theme stylesheet.
  1038. * @param {deleteThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.deleteThemeSuccess
  1039. * @param {deleteThemeError=} args.error Optional. Error callback. Default: wp.updates.deleteThemeError
  1040. * @return {$.promise} A jQuery promise that represents the request,
  1041. * decorated with an abort() method.
  1042. */
  1043. wp.updates.deleteTheme = function( args ) {
  1044. var $button;
  1045. if ( 'themes' === pagenow ) {
  1046. $button = $( '.theme-actions .delete-theme' );
  1047. } else if ( 'themes-network' === pagenow ) {
  1048. $button = $( '[data-slug="' + args.slug + '"]' ).find( '.row-actions a.delete' );
  1049. }
  1050. args = _.extend( {
  1051. success: wp.updates.deleteThemeSuccess,
  1052. error: wp.updates.deleteThemeError
  1053. }, args );
  1054. if ( $button && $button.html() !== wp.updates.l10n.deleting ) {
  1055. $button
  1056. .data( 'originaltext', $button.html() )
  1057. .text( wp.updates.l10n.deleting );
  1058. }
  1059. wp.a11y.speak( wp.updates.l10n.deleting, 'polite' );
  1060. // Remove previous error messages, if any.
  1061. $( '.theme-info .update-message' ).remove();
  1062. $document.trigger( 'wp-theme-deleting', args );
  1063. return wp.updates.ajax( 'delete-theme', args );
  1064. };
  1065. /**
  1066. * Updates the UI appropriately after a successful theme deletion.
  1067. *
  1068. * @since 4.6.0
  1069. *
  1070. * @param {object} response Response from the server.
  1071. * @param {string} response.slug Slug of the theme that was deleted.
  1072. */
  1073. wp.updates.deleteThemeSuccess = function( response ) {
  1074. var $themeRows = $( '[data-slug="' + response.slug + '"]' );
  1075. if ( 'themes-network' === pagenow ) {
  1076. // Removes the theme and updates rows.
  1077. $themeRows.css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
  1078. var $views = $( '.subsubsub' ),
  1079. $themeRow = $( this ),
  1080. totals = settings.themes,
  1081. deletedRow = wp.template( 'item-deleted-row' );
  1082. if ( ! $themeRow.hasClass( 'plugin-update-tr' ) ) {
  1083. $themeRow.after(
  1084. deletedRow( {
  1085. slug: response.slug,
  1086. colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
  1087. name: $themeRow.find( '.theme-title strong' ).text()
  1088. } )
  1089. );
  1090. }
  1091. $themeRow.remove();
  1092. // Remove theme from update count.
  1093. if ( $themeRow.hasClass( 'update' ) ) {
  1094. totals.upgrade--;
  1095. wp.updates.decrementCount( 'theme' );
  1096. }
  1097. // Remove from views.
  1098. if ( $themeRow.hasClass( 'inactive' ) ) {
  1099. totals.disabled--;
  1100. if ( totals.disabled ) {
  1101. $views.find( '.disabled .count' ).text( '(' + totals.disabled + ')' );
  1102. } else {
  1103. $views.find( '.disabled' ).remove();
  1104. }
  1105. }
  1106. // There is always at least one theme available.
  1107. $views.find( '.all .count' ).text( '(' + --totals.all + ')' );
  1108. } );
  1109. }
  1110. wp.a11y.speak( wp.updates.l10n.themeDeleted, 'polite' );
  1111. $document.trigger( 'wp-theme-delete-success', response );
  1112. };
  1113. /**
  1114. * Updates the UI appropriately after a failed theme deletion.
  1115. *
  1116. * @since 4.6.0
  1117. *
  1118. * @param {object} response Response from the server.
  1119. * @param {string} response.slug Slug of the theme to be deleted.
  1120. * @param {string} response.errorCode Error code for the error that occurred.
  1121. * @param {string} response.errorMessage The error that occurred.
  1122. */
  1123. wp.updates.deleteThemeError = function( response ) {
  1124. var $themeRow = $( 'tr.inactive[data-slug="' + response.slug + '"]' ),
  1125. $button = $( '.theme-actions .delete-theme' ),
  1126. updateRow = wp.template( 'item-update-row' ),
  1127. $updateRow = $themeRow.siblings( '#' + response.slug + '-update' ),
  1128. errorMessage = wp.updates.l10n.deleteFailed.replace( '%s', response.errorMessage ),
  1129. $message = wp.updates.adminNotice( {
  1130. className: 'update-message notice-error notice-alt',
  1131. message: errorMessage
  1132. } );
  1133. if ( wp.updates.maybeHandleCredentialError( response, 'delete-theme' ) ) {
  1134. return;
  1135. }
  1136. if ( 'themes-network' === pagenow ) {
  1137. if ( ! $updateRow.length ) {
  1138. $themeRow.addClass( 'update' ).after(
  1139. updateRow( {
  1140. slug: response.slug,
  1141. colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
  1142. content: $message
  1143. } )
  1144. );
  1145. } else {
  1146. // Remove previous error messages, if any.
  1147. $updateRow.find( '.notice-error' ).remove();
  1148. $updateRow.find( '.plugin-update' ).append( $message );
  1149. }
  1150. } else {
  1151. $( '.theme-info .theme-description' ).before( $message );
  1152. }
  1153. $button.html( $button.data( 'originaltext' ) );
  1154. wp.a11y.speak( errorMessage, 'assertive' );
  1155. $document.trigger( 'wp-theme-delete-error', response );
  1156. };
  1157. /**
  1158. * Adds the appropriate callback based on the type of action and the current page.
  1159. *
  1160. * @since 4.6.0
  1161. * @private
  1162. *
  1163. * @param {object} data AJAX payload.
  1164. * @param {string} action The type of request to perform.
  1165. * @return {object} The AJAX payload with the appropriate callbacks.
  1166. */
  1167. wp.updates._addCallbacks = function( data, action ) {
  1168. if ( 'import' === pagenow && 'install-plugin' === action ) {
  1169. data.success = wp.updates.installImporterSuccess;
  1170. data.error = wp.updates.installImporterError;
  1171. }
  1172. return data;
  1173. };
  1174. /**
  1175. * Pulls available jobs from the queue and runs them.
  1176. *
  1177. * @since 4.2.0
  1178. * @since 4.6.0 Can handle multiple job types.
  1179. */
  1180. wp.updates.queueChecker = function() {
  1181. var job;
  1182. if ( wp.updates.ajaxLocked || ! wp.updates.queue.length ) {
  1183. return;
  1184. }
  1185. job = wp.updates.queue.shift();
  1186. // Handle a queue job.
  1187. switch ( job.action ) {
  1188. case 'install-plugin':
  1189. wp.updates.installPlugin( job.data );
  1190. break;
  1191. case 'update-plugin':
  1192. wp.updates.updatePlugin( job.data );
  1193. break;
  1194. case 'delete-plugin':
  1195. wp.updates.deletePlugin( job.data );
  1196. break;
  1197. case 'install-theme':
  1198. wp.updates.installTheme( job.data );
  1199. break;
  1200. case 'update-theme':
  1201. wp.updates.updateTheme( job.data );
  1202. break;
  1203. case 'delete-theme':
  1204. wp.updates.deleteTheme( job.data );
  1205. break;
  1206. default:
  1207. break;
  1208. }
  1209. };
  1210. /**
  1211. * Requests the users filesystem credentials if they aren't already known.
  1212. *
  1213. * @since 4.2.0
  1214. *
  1215. * @param {Event=} event Optional. Event interface.
  1216. */
  1217. wp.updates.requestFilesystemCredentials = function( event ) {
  1218. if ( false === wp.updates.filesystemCredentials.available ) {
  1219. /*
  1220. * After exiting the credentials request modal,
  1221. * return the focus to the element triggering the request.
  1222. */
  1223. if ( event && ! wp.updates.$elToReturnFocusToFromCredentialsModal ) {
  1224. wp.updates.$elToReturnFocusToFromCredentialsModal = $( event.target );
  1225. }
  1226. wp.updates.ajaxLocked = true;
  1227. wp.updates.requestForCredentialsModalOpen();
  1228. }
  1229. };
  1230. /**
  1231. * Requests the users filesystem credentials if needed and there is no lock.
  1232. *
  1233. * @since 4.6.0
  1234. *
  1235. * @param {Event=} event Optional. Event interface.
  1236. */
  1237. wp.updates.maybeRequestFilesystemCredentials = function( event ) {
  1238. if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
  1239. wp.updates.requestFilesystemCredentials( event );
  1240. }
  1241. };
  1242. /**
  1243. * Keydown handler for the request for credentials modal.
  1244. *
  1245. * Closes the modal when the escape key is pressed and
  1246. * constrains keyboard navigation to inside the modal.
  1247. *
  1248. * @since 4.2.0
  1249. *
  1250. * @param {Event} event Event interface.
  1251. */
  1252. wp.updates.keydown = function( event ) {
  1253. if ( 27 === event.keyCode ) {
  1254. wp.updates.requestForCredentialsModalCancel();
  1255. } else if ( 9 === event.keyCode ) {
  1256. // #upgrade button must always be the last focus-able element in the dialog.
  1257. if ( 'upgrade' === event.target.id && ! event.shiftKey ) {
  1258. $( '#hostname' ).focus();
  1259. event.preventDefault();
  1260. } else if ( 'hostname' === event.target.id && event.shiftKey ) {
  1261. $( '#upgrade' ).focus();
  1262. event.preventDefault();
  1263. }
  1264. }
  1265. };
  1266. /**
  1267. * Opens the request for credentials modal.
  1268. *
  1269. * @since 4.2.0
  1270. */
  1271. wp.updates.requestForCredentialsModalOpen = function() {
  1272. var $modal = $( '#request-filesystem-credentials-dialog' );
  1273. $( 'body' ).addClass( 'modal-open' );
  1274. $modal.show();
  1275. $modal.find( 'input:enabled:first' ).focus();
  1276. $modal.on( 'keydown', wp.updates.keydown );
  1277. };
  1278. /**
  1279. * Closes the request for credentials modal.
  1280. *
  1281. * @since 4.2.0
  1282. */
  1283. wp.updates.requestForCredentialsModalClose = function() {
  1284. $( '#request-filesystem-credentials-dialog' ).hide();
  1285. $( 'body' ).removeClass( 'modal-open' );
  1286. if ( wp.updates.$elToReturnFocusToFromCredentialsModal ) {
  1287. wp.updates.$elToReturnFocusToFromCredentialsModal.focus();
  1288. }
  1289. };
  1290. /**
  1291. * Takes care of the steps that need to happen when the modal is canceled out.
  1292. *
  1293. * @since 4.2.0
  1294. * @since 4.6.0 Triggers an event for callbacks to listen to and add their actions.
  1295. */
  1296. wp.updates.requestForCredentialsModalCancel = function() {
  1297. // Not ajaxLocked and no queue means we already have cleared things up.
  1298. if ( ! wp.updates.ajaxLocked && ! wp.updates.queue.length ) {
  1299. return;
  1300. }
  1301. _.each( wp.updates.queue, function( job ) {
  1302. $document.trigger( 'credential-modal-cancel', job );
  1303. } );
  1304. // Remove the lock, and clear the queue.
  1305. wp.updates.ajaxLocked = false;
  1306. wp.updates.queue = [];
  1307. wp.updates.requestForCredentialsModalClose();
  1308. };
  1309. /**
  1310. * Displays an error message in the request for credentials form.
  1311. *
  1312. * @since 4.2.0
  1313. *
  1314. * @param {string} message Error message.
  1315. */
  1316. wp.updates.showErrorInCredentialsForm = function( message ) {
  1317. var $filesystemForm = $( '#request-filesystem-credentials-form' );
  1318. // Remove any existing error.
  1319. $filesystemForm.find( '.notice' ).remove();
  1320. $filesystemForm.find( '#request-filesystem-credentials-title' ).after( '<div class="notice notice-alt notice-error"><p>' + message + '</p></div>' );
  1321. };
  1322. /**
  1323. * Handles credential errors and runs events that need to happen in that case.
  1324. *
  1325. * @since 4.2.0
  1326. *
  1327. * @param {object} response Ajax response.
  1328. * @param {string} action The type of request to perform.
  1329. */
  1330. wp.updates.credentialError = function( response, action ) {
  1331. // Restore callbacks.
  1332. response = wp.updates._addCallbacks( response, action );
  1333. wp.updates.queue.unshift( {
  1334. action: action,
  1335. /*
  1336. * Not cool that we're depending on response for this data.
  1337. * This would feel more whole in a view all tied together.
  1338. */
  1339. data: response
  1340. } );
  1341. wp.updates.filesystemCredentials.available = false;
  1342. wp.updates.showErrorInCredentialsForm( response.errorMessage );
  1343. wp.updates.requestFilesystemCredentials();
  1344. };
  1345. /**
  1346. * Handles credentials errors if it could not connect to the filesystem.
  1347. *
  1348. * @since 4.6.0
  1349. *
  1350. * @param {object} response Response from the server.
  1351. * @param {string} response.errorCode Error code for the error that occurred.
  1352. * @param {string} response.errorMessage The error that occurred.
  1353. * @param {string} action The type of request to perform.
  1354. * @returns {boolean} Whether there is an error that needs to be handled or not.
  1355. */
  1356. wp.updates.maybeHandleCredentialError = function( response, action ) {
  1357. if ( wp.updates.shouldRequestFilesystemCredentials && response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) {
  1358. wp.updates.credentialError( response, action );
  1359. return true;
  1360. }
  1361. return false;
  1362. };
  1363. /**
  1364. * Validates an AJAX response to ensure it's a proper object.
  1365. *
  1366. * If the response deems to be invalid, an admin notice is being displayed.
  1367. *
  1368. * @param {(object|string)} response Response from the server.
  1369. * @param {function=} response.always Optional. Callback for when the Deferred is resolved or rejected.
  1370. * @param {string=} response.statusText Optional. Status message corresponding to the status code.
  1371. * @param {string=} response.responseText Optional. Request response as text.
  1372. * @param {string} action Type of action the response is referring to. Can be 'delete',
  1373. * 'update' or 'install'.
  1374. */
  1375. wp.updates.isValidResponse = function( response, action ) {
  1376. var error = wp.updates.l10n.unknownError,
  1377. errorMessage;
  1378. // Make sure the response is a valid data object and not a Promise object.
  1379. if ( _.isObject( response ) && ! _.isFunction( response.always ) ) {
  1380. return true;
  1381. }
  1382. if ( _.isString( response ) && '-1' === response ) {
  1383. error = wp.updates.l10n.nonceError;
  1384. } else if ( _.isString( response ) ) {
  1385. error = response;
  1386. } else if ( 'undefined' !== typeof response.readyState && 0 === response.readyState ) {
  1387. error = wp.updates.l10n.connectionError;
  1388. } else if ( _.isString( response.responseText ) && '' !== response.responseText ) {
  1389. error = response.responseText;
  1390. } else if ( _.isString( response.statusText ) ) {
  1391. error = response.statusText;
  1392. }
  1393. switch ( action ) {
  1394. case 'update':
  1395. errorMessage = wp.updates.l10n.updateFailed;
  1396. break;
  1397. case 'install':
  1398. errorMessage = wp.updates.l10n.installFailed;
  1399. break;
  1400. case 'delete':
  1401. errorMessage = wp.updates.l10n.deleteFailed;
  1402. break;
  1403. }
  1404. // Messages are escaped, remove HTML tags to make them more readable.
  1405. error = error.replace( /<[\/a-z][^<>]*>/gi, '' );
  1406. errorMessage = errorMessage.replace( '%s', error );
  1407. // Add admin notice.
  1408. wp.updates.addAdminNotice( {
  1409. id: 'unknown_error',
  1410. className: 'notice-error is-dismissible',
  1411. message: _.escape( errorMessage )
  1412. } );
  1413. // Remove the lock, and clear the queue.
  1414. wp.updates.ajaxLocked = false;
  1415. wp.updates.queue = [];
  1416. // Change buttons of all running updates.
  1417. $( '.button.updating-message' )
  1418. .removeClass( 'updating-message' )
  1419. .removeAttr( 'aria-label' )
  1420. .prop( 'disabled', true )
  1421. .text( wp.updates.l10n.updateFailedShort );
  1422. $( '.updating-message:not(.button):not(.thickbox)' )
  1423. .removeClass( 'updating-message notice-warning' )
  1424. .addClass( 'notice-error' )
  1425. .find( 'p' )
  1426. .removeAttr( 'aria-label' )
  1427. .text( errorMessage );
  1428. wp.a11y.speak( errorMessage, 'assertive' );
  1429. return false;
  1430. };
  1431. /**
  1432. * Potentially adds an AYS to a user attempting to leave the page.
  1433. *
  1434. * If an update is on-going and a user attempts to leave the page,
  1435. * opens an "Are you sure?" alert.
  1436. *
  1437. * @since 4.2.0
  1438. */
  1439. wp.updates.beforeunload = function() {
  1440. if ( wp.updates.ajaxLocked ) {
  1441. return wp.updates.l10n.beforeunload;
  1442. }
  1443. };
  1444. $( function() {
  1445. var $pluginFilter = $( '#plugin-filter' ),
  1446. $bulkActionForm = $( '#bulk-action-form' ),
  1447. $filesystemForm = $( '#request-filesystem-credentials-form' ),
  1448. $filesystemModal = $( '#request-filesystem-credentials-dialog' ),
  1449. $pluginSearch = $( '.plugins-php .wp-filter-search' ),
  1450. $pluginInstallSearch = $( '.plugin-install-php .wp-filter-search' );
  1451. settings = _.extend( settings, window._wpUpdatesItemCounts || {} );
  1452. if ( settings.totals ) {
  1453. wp.updates.refreshCount();
  1454. }
  1455. /*
  1456. * Whether a user needs to submit filesystem credentials.
  1457. *
  1458. * This is based on whether the form was output on the page server-side.
  1459. *
  1460. * @see {wp_print_request_filesystem_credentials_modal() in PHP}
  1461. */
  1462. wp.updates.shouldRequestFilesystemCredentials = $filesystemModal.length > 0;
  1463. /**
  1464. * File system credentials form submit noop-er / handler.
  1465. *
  1466. * @since 4.2.0
  1467. */
  1468. $filesystemModal.on( 'submit', 'form', function( event ) {
  1469. event.preventDefault();
  1470. // Persist the credentials input by the user for the duration of the page load.
  1471. wp.updates.filesystemCredentials.ftp.hostname = $( '#hostname' ).val();
  1472. wp.updates.filesystemCredentials.ftp.username = $( '#username' ).val();
  1473. wp.updates.filesystemCredentials.ftp.password = $( '#password' ).val();
  1474. wp.updates.filesystemCredentials.ftp.connectionType = $( 'input[name="connection_type"]:checked' ).val();
  1475. wp.updates.filesystemCredentials.ssh.publicKey = $( '#public_key' ).val();
  1476. wp.updates.filesystemCredentials.ssh.privateKey = $( '#private_key' ).val();
  1477. wp.updates.filesystemCredentials.fsNonce = $( '#_fs_nonce' ).val();
  1478. wp.updates.filesystemCredentials.available = true;
  1479. // Unlock and invoke the queue.
  1480. wp.updates.ajaxLocked = false;
  1481. wp.updates.queueChecker();
  1482. wp.updates.requestForCredentialsModalClose();
  1483. } );
  1484. /**
  1485. * Closes the request credentials modal when clicking the 'Cancel' button or outside of the modal.
  1486. *
  1487. * @since 4.2.0
  1488. */
  1489. $filesystemModal.on( 'click', '[data-js-action="close"], .notification-dialog-background', wp.updates.requestForCredentialsModalCancel );
  1490. /**
  1491. * Hide SSH fields when not selected.
  1492. *
  1493. * @since 4.2.0
  1494. */
  1495. $filesystemForm.on( 'change', 'input[name="connection_type"]', function() {
  1496. $( '#ssh-keys' ).toggleClass( 'hidden', ( 'ssh' !== $( this ).val() ) );
  1497. } ).change();
  1498. /**
  1499. * Handles events after the credential modal was closed.
  1500. *
  1501. * @since 4.6.0
  1502. *
  1503. * @param {Event} event Event interface.
  1504. * @param {string} job The install/update.delete request.
  1505. */
  1506. $document.on( 'credential-modal-cancel', function( event, job ) {
  1507. var $updatingMessage = $( '.updating-message' ),
  1508. $message, originalText;
  1509. if ( 'import' === pagenow ) {
  1510. $updatingMessage.removeClass( 'updating-message' );
  1511. } else if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
  1512. if ( 'update-plugin' === job.action ) {
  1513. $message = $( 'tr[data-plugin="' + job.data.plugin + '"]' ).find( '.update-message' );
  1514. } else if ( 'delete-plugin' === job.action ) {
  1515. $message = $( '[data-plugin="' + job.data.plugin + '"]' ).find( '.row-actions a.delete' );
  1516. }
  1517. } else if ( 'themes' === pagenow || 'themes-network' === pagenow ) {
  1518. if ( 'update-theme' === job.action ) {
  1519. $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.update-message' );
  1520. } else if ( 'delete-theme' === job.action && 'themes-network' === pagenow ) {
  1521. $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.row-actions a.delete' );
  1522. } else if ( 'delete-theme' === job.action && 'themes' === pagenow ) {
  1523. $message = $( '.theme-actions .delete-theme' );
  1524. }
  1525. } else {
  1526. $message = $updatingMessage;
  1527. }
  1528. if ( $message && $message.hasClass( 'updating-message' ) ) {
  1529. originalText = $message.data( 'originaltext' );
  1530. if ( 'undefined' === typeof originalText ) {
  1531. originalText = $( '<p>' ).html( $message.find( 'p' ).data( 'originaltext' ) );
  1532. }
  1533. $message
  1534. .removeClass( 'updating-message' )
  1535. .html( originalText );
  1536. if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
  1537. if ( 'update-plugin' === job.action ) {
  1538. $message.attr( 'aria-label', wp.updates.l10n.pluginUpdateNowLabel.replace( '%s', $message.data( 'name' ) ) );
  1539. } else if ( 'install-plugin' === job.action ) {
  1540. $message.attr( 'aria-label', wp.updates.l10n.pluginInstallNowLabel.replace( '%s', $message.data( 'name' ) ) );
  1541. }
  1542. }
  1543. }
  1544. wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
  1545. } );
  1546. /**
  1547. * Click handler for plugin updates in List Table view.
  1548. *
  1549. * @since 4.2.0
  1550. *
  1551. * @param {Event} event Event interface.
  1552. */
  1553. $bulkActionForm.on( 'click', '[data-plugin] .update-link', function( event ) {
  1554. var $message = $( event.target ),
  1555. $pluginRow = $message.parents( 'tr' );
  1556. event.preventDefault();
  1557. if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
  1558. return;
  1559. }
  1560. wp.updates.maybeRequestFilesystemCredentials( event );
  1561. // Return the user to the input box of the plugin's table row after closing the modal.
  1562. wp.updates.$elToReturnFocusToFromCredentialsModal = $pluginRow.find( '.check-column input' );
  1563. wp.updates.updatePlugin( {
  1564. plugin: $pluginRow.data( 'plugin' ),
  1565. slug: $pluginRow.data( 'slug' )
  1566. } );
  1567. } );
  1568. /**
  1569. * Click handler for plugin updates in plugin install view.
  1570. *
  1571. * @since 4.2.0
  1572. *
  1573. * @param {Event} event Event interface.
  1574. */
  1575. $pluginFilter.on( 'click', '.update-now', function( event ) {
  1576. var $button = $( event.target );
  1577. event.preventDefault();
  1578. if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
  1579. return;
  1580. }
  1581. wp.updates.maybeRequestFilesystemCredentials( event );
  1582. wp.updates.updatePlugin( {
  1583. plugin: $button.data( 'plugin' ),
  1584. slug: $button.data( 'slug' )
  1585. } );
  1586. } );
  1587. /**
  1588. * Click handler for plugin installs in plugin install view.
  1589. *
  1590. * @since 4.6.0
  1591. *
  1592. * @param {Event} event Event interface.
  1593. */
  1594. $pluginFilter.on( 'click', '.install-now', function( event ) {
  1595. var $button = $( event.target );
  1596. event.preventDefault();
  1597. if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
  1598. return;
  1599. }
  1600. if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
  1601. wp.updates.requestFilesystemCredentials( event );
  1602. $document.on( 'credential-modal-cancel', function() {
  1603. var $message = $( '.install-now.updating-message' );
  1604. $message
  1605. .removeClass( 'updating-message' )
  1606. .text( wp.updates.l10n.installNow );
  1607. wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
  1608. } );
  1609. }
  1610. wp.updates.installPlugin( {
  1611. slug: $button.data( 'slug' )
  1612. } );
  1613. } );
  1614. /**
  1615. * Click handler for importer plugins installs in the Import screen.
  1616. *
  1617. * @since 4.6.0
  1618. *
  1619. * @param {Event} event Event interface.
  1620. */
  1621. $document.on( 'click', '.importer-item .install-now', function( event ) {
  1622. var $button = $( event.target ),
  1623. pluginName = $( this ).data( 'name' );
  1624. event.preventDefault();
  1625. if ( $button.hasClass( 'updating-message' ) ) {
  1626. return;
  1627. }
  1628. if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
  1629. wp.updates.requestFilesystemCredentials( event );
  1630. $document.on( 'credential-modal-cancel', function() {
  1631. $button
  1632. .removeClass( 'updating-message' )
  1633. .text( wp.updates.l10n.installNow )
  1634. .attr( 'aria-label', wp.updates.l10n.pluginInstallNowLabel.replace( '%s', pluginName ) );
  1635. wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
  1636. } );
  1637. }
  1638. wp.updates.installPlugin( {
  1639. slug: $button.data( 'slug' ),
  1640. pagenow: pagenow,
  1641. success: wp.updates.installImporterSuccess,
  1642. error: wp.updates.installImporterError
  1643. } );
  1644. } );
  1645. /**
  1646. * Click handler for plugin deletions.
  1647. *
  1648. * @since 4.6.0
  1649. *
  1650. * @param {Event} event Event interface.
  1651. */
  1652. $bulkActionForm.on( 'click', '[data-plugin] a.delete', function( event ) {
  1653. var $pluginRow = $( event.target ).parents( 'tr' );
  1654. event.preventDefault();
  1655. if ( ! window.confirm( wp.updates.l10n.aysDeleteUninstall.replace( '%s', $pluginRow.find( '.plugin-title strong' ).text() ) ) ) {
  1656. return;
  1657. }
  1658. wp.updates.maybeRequestFilesystemCredentials( event );
  1659. wp.updates.deletePlugin( {
  1660. plugin: $pluginRow.data( 'plugin' ),
  1661. slug: $pluginRow.data( 'slug' )
  1662. } );
  1663. } );
  1664. /**
  1665. * Click handler for theme updates.
  1666. *
  1667. * @since 4.6.0
  1668. *
  1669. * @param {Event} event Event interface.
  1670. */
  1671. $document.on( 'click', '.themes-php.network-admin .update-link', function( event ) {
  1672. var $message = $( event.target ),
  1673. $themeRow = $message.parents( 'tr' );
  1674. event.preventDefault();
  1675. if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
  1676. return;
  1677. }
  1678. wp.updates.maybeRequestFilesystemCredentials( event );
  1679. // Return the user to the input box of the theme's table row after closing the modal.
  1680. wp.updates.$elToReturnFocusToFromCredentialsModal = $themeRow.find( '.check-column input' );
  1681. wp.updates.updateTheme( {
  1682. slug: $themeRow.data( 'slug' )
  1683. } );
  1684. } );
  1685. /**
  1686. * Click handler for theme deletions.
  1687. *
  1688. * @since 4.6.0
  1689. *
  1690. * @param {Event} event Event interface.
  1691. */
  1692. $document.on( 'click', '.themes-php.network-admin a.delete', function( event ) {
  1693. var $themeRow = $( event.target ).parents( 'tr' );
  1694. event.preventDefault();
  1695. if ( ! window.confirm( wp.updates.l10n.aysDelete.replace( '%s', $themeRow.find( '.theme-title strong' ).text() ) ) ) {
  1696. return;
  1697. }
  1698. wp.updates.maybeRequestFilesystemCredentials( event );
  1699. wp.updates.deleteTheme( {
  1700. slug: $themeRow.data( 'slug' )
  1701. } );
  1702. } );
  1703. /**
  1704. * Bulk action handler for plugins and themes.
  1705. *
  1706. * Handles both deletions and updates.
  1707. *
  1708. * @since 4.6.0
  1709. *
  1710. * @param {Event} event Event interface.
  1711. */
  1712. $bulkActionForm.on( 'click', '[type="submit"]:not([name="clear-recent-list"])', function( event ) {
  1713. var bulkAction = $( event.target ).siblings( 'select' ).val(),
  1714. itemsSelected = $bulkActionForm.find( 'input[name="checked[]"]:checked' ),
  1715. success = 0,
  1716. error = 0,
  1717. errorMessages = [],
  1718. type, action;
  1719. // Determine which type of item we're dealing with.
  1720. switch ( pagenow ) {
  1721. case 'plugins':
  1722. case 'plugins-network':
  1723. type = 'plugin';
  1724. break;
  1725. case 'themes-network':
  1726. type = 'theme';
  1727. break;
  1728. default:
  1729. return;
  1730. }
  1731. // Bail if there were no items selected.
  1732. if ( ! itemsSelected.length ) {
  1733. event.preventDefault();
  1734. $( 'html, body' ).animate( { scrollTop: 0 } );
  1735. return wp.updates.addAdminNotice( {
  1736. id: 'no-items-selected',
  1737. className: 'notice-error is-dismissible',
  1738. message: wp.updates.l10n.noItemsSelected
  1739. } );
  1740. }
  1741. // Determine the type of request we're dealing with.
  1742. switch ( bulkAction ) {
  1743. case 'update-selected':
  1744. action = bulkAction.replace( 'selected', type );
  1745. break;
  1746. case 'delete-selected':
  1747. if ( ! window.confirm( 'plugin' === type ? wp.updates.l10n.aysBulkDelete : wp.updates.l10n.aysBulkDeleteThemes ) ) {
  1748. event.preventDefault();
  1749. return;
  1750. }
  1751. action = bulkAction.replace( 'selected', type );
  1752. break;
  1753. default:
  1754. return;
  1755. }
  1756. wp.updates.maybeRequestFilesystemCredentials( event );
  1757. event.preventDefault();
  1758. // Un-check the bulk checkboxes.
  1759. $bulkActionForm.find( '.manage-column [type="checkbox"]' ).prop( 'checked', false );
  1760. $document.trigger( 'wp-' + type + '-bulk-' + bulkAction, itemsSelected );
  1761. // Find all the checkboxes which have been checked.
  1762. itemsSelected.each( function( index, element ) {
  1763. var $checkbox = $( element ),
  1764. $itemRow = $checkbox.parents( 'tr' );
  1765. // Only add update-able items to the update queue.
  1766. if ( 'update-selected' === bulkAction && ( ! $itemRow.hasClass( 'update' ) || $itemRow.find( 'notice-error' ).length ) ) {
  1767. // Un-check the box.
  1768. $checkbox.prop( 'checked', false );
  1769. return;
  1770. }
  1771. // Add it to the queue.
  1772. wp.updates.queue.push( {
  1773. action: action,
  1774. data: {
  1775. plugin: $itemRow.data( 'plugin' ),
  1776. slug: $itemRow.data( 'slug' )
  1777. }
  1778. } );
  1779. } );
  1780. // Display bulk notification for updates of any kind.
  1781. $document.on( 'wp-plugin-update-success wp-plugin-update-error wp-theme-update-success wp-theme-update-error', function( event, response ) {
  1782. var $itemRow = $( '[data-slug="' + response.slug + '"]' ),
  1783. $bulkActionNotice, itemName;
  1784. if ( 'wp-' + response.update + '-update-success' === event.type ) {
  1785. success++;
  1786. } else {
  1787. itemName = response.pluginName ? response.pluginName : $itemRow.find( '.column-primary strong' ).text();
  1788. error++;
  1789. errorMessages.push( itemName + ': ' + response.errorMessage );
  1790. }
  1791. $itemRow.find( 'input[name="checked[]"]:checked' ).prop( 'checked', false );
  1792. wp.updates.adminNotice = wp.template( 'wp-bulk-updates-admin-notice' );
  1793. wp.updates.addAdminNotice( {
  1794. id: 'bulk-action-notice',
  1795. className: 'bulk-action-notice',
  1796. successes: success,
  1797. errors: error,
  1798. errorMessages: errorMessages,
  1799. type: response.update
  1800. } );
  1801. $bulkActionNotice = $( '#bulk-action-notice' ).on( 'click', 'button', function() {
  1802. // $( this ) is the clicked button, no need to get it again.
  1803. $( this )
  1804. .toggleClass( 'bulk-action-errors-collapsed' )
  1805. .attr( 'aria-expanded', ! $( this ).hasClass( 'bulk-action-errors-collapsed' ) );
  1806. // Show the errors list.
  1807. $bulkActionNotice.find( '.bulk-action-errors' ).toggleClass( 'hidden' );
  1808. } );
  1809. if ( error > 0 && ! wp.updates.queue.length ) {
  1810. $( 'html, body' ).animate( { scrollTop: 0 } );
  1811. }
  1812. } );
  1813. // Reset admin notice template after #bulk-action-notice was added.
  1814. $document.on( 'wp-updates-notice-added', function() {
  1815. wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
  1816. } );
  1817. // Check the queue, now that the event handlers have been added.
  1818. wp.updates.queueChecker();
  1819. } );
  1820. if ( $pluginInstallSearch.length ) {
  1821. $pluginInstallSearch.attr( 'aria-describedby', 'live-search-desc' );
  1822. }
  1823. /**
  1824. * Handles changes to the plugin search box on the new-plugin page,
  1825. * searching the repository dynamically.
  1826. *
  1827. * @since 4.6.0
  1828. */
  1829. $pluginInstallSearch.on( 'keyup input', _.debounce( function( event, eventtype ) {
  1830. var $searchTab = $( '.plugin-install-search' ), data, searchLocation;
  1831. data = {
  1832. _ajax_nonce: wp.updates.ajaxNonce,
  1833. s: event.target.value,
  1834. tab: 'search',
  1835. type: $( '#typeselector' ).val(),
  1836. pagenow: pagenow
  1837. };
  1838. searchLocation = location.href.split( '?' )[ 0 ] + '?' + $.param( _.omit( data, [ '_ajax_nonce', 'pagenow' ] ) );
  1839. // Clear on escape.
  1840. if ( 'keyup' === event.type && 27 === event.which ) {
  1841. event.target.value = '';
  1842. }
  1843. if ( wp.updates.searchTerm === data.s && 'typechange' !== eventtype ) {
  1844. return;
  1845. } else {
  1846. $pluginFilter.empty();
  1847. wp.updates.searchTerm = data.s;
  1848. }
  1849. if ( window.history && window.history.replaceState ) {
  1850. window.history.replaceState( null, '', searchLocation );
  1851. }
  1852. if ( ! $searchTab.length ) {
  1853. $searchTab = $( '<li class="plugin-install-search" />' )
  1854. .append( $( '<a />', {
  1855. 'class': 'current',
  1856. 'href': searchLocation,
  1857. 'text': wp.updates.l10n.searchResultsLabel
  1858. } ) );
  1859. $( '.wp-filter .filter-links .current' )
  1860. .removeClass( 'current' )
  1861. .parents( '.filter-links' )
  1862. .prepend( $searchTab );
  1863. $pluginFilter.prev( 'p' ).remove();
  1864. $( '.plugins-popular-tags-wrapper' ).remove();
  1865. }
  1866. if ( 'undefined' !== typeof wp.updates.searchRequest ) {
  1867. wp.updates.searchRequest.abort();
  1868. }
  1869. $( 'body' ).addClass( 'loading-content' );
  1870. wp.updates.searchRequest = wp.ajax.post( 'search-install-plugins', data ).done( function( response ) {
  1871. $( 'body' ).removeClass( 'loading-content' );
  1872. $pluginFilter.append( response.items );
  1873. delete wp.updates.searchRequest;
  1874. if ( 0 === response.count ) {
  1875. wp.a11y.speak( wp.updates.l10n.noPluginsFound );
  1876. } else {
  1877. wp.a11y.speak( wp.updates.l10n.pluginsFound.replace( '%d', response.count ) );
  1878. }
  1879. } );
  1880. }, 1000 ) );
  1881. if ( $pluginSearch.length ) {
  1882. $pluginSearch.attr( 'aria-describedby', 'live-search-desc' );
  1883. }
  1884. /**
  1885. * Handles changes to the plugin search box on the Installed Plugins screen,
  1886. * searching the plugin list dynamically.
  1887. *
  1888. * @since 4.6.0
  1889. */
  1890. $pluginSearch.on( 'keyup input', _.debounce( function( event ) {
  1891. var data = {
  1892. _ajax_nonce: wp.updates.ajaxNonce,
  1893. s: event.target.value,
  1894. pagenow: pagenow,
  1895. plugin_status: 'all'
  1896. },
  1897. queryArgs;
  1898. // Clear on escape.
  1899. if ( 'keyup' === event.type && 27 === event.which ) {
  1900. event.target.value = '';
  1901. }
  1902. if ( wp.updates.searchTerm === data.s ) {
  1903. return;
  1904. } else {
  1905. wp.updates.searchTerm = data.s;
  1906. }
  1907. queryArgs = _.object( _.compact( _.map( location.search.slice( 1 ).split( '&' ), function( item ) {
  1908. if ( item ) return item.split( '=' );
  1909. } ) ) );
  1910. data.plugin_status = queryArgs.plugin_status || 'all';
  1911. if ( window.history && window.history.replaceState ) {
  1912. window.history.replaceState( null, '', location.href.split( '?' )[ 0 ] + '?s=' + data.s + '&plugin_status=' + data.plugin_status );
  1913. }
  1914. if ( 'undefined' !== typeof wp.updates.searchRequest ) {
  1915. wp.updates.searchRequest.abort();
  1916. }
  1917. $bulkActionForm.empty();
  1918. $( 'body' ).addClass( 'loading-content' );
  1919. $( '.subsubsub .current' ).removeClass( 'current' );
  1920. wp.updates.searchRequest = wp.ajax.post( 'search-plugins', data ).done( function( response ) {
  1921. // Can we just ditch this whole subtitle business?
  1922. var $subTitle = $( '<span />' ).addClass( 'subtitle' ).html( wp.updates.l10n.searchResults.replace( '%s', _.escape( data.s ) ) ),
  1923. $oldSubTitle = $( '.wrap .subtitle' );
  1924. if ( ! data.s.length ) {
  1925. $oldSubTitle.remove();
  1926. $( '.subsubsub .' + data.plugin_status + ' a' ).addClass( 'current' );
  1927. } else if ( $oldSubTitle.length ) {
  1928. $oldSubTitle.replaceWith( $subTitle );
  1929. } else {
  1930. $( '.wp-header-end' ).before( $subTitle );
  1931. }
  1932. $( 'body' ).removeClass( 'loading-content' );
  1933. $bulkActionForm.append( response.items );
  1934. delete wp.updates.searchRequest;
  1935. if ( 0 === response.count ) {
  1936. wp.a11y.speak( wp.updates.l10n.noPluginsFound );
  1937. } else {
  1938. wp.a11y.speak( wp.updates.l10n.pluginsFound.replace( '%d', response.count ) );
  1939. }
  1940. } );
  1941. }, 500 ) );
  1942. /**
  1943. * Trigger a search event when the search form gets submitted.
  1944. *
  1945. * @since 4.6.0
  1946. */
  1947. $document.on( 'submit', '.search-plugins', function( event ) {
  1948. event.preventDefault();
  1949. $( 'input.wp-filter-search' ).trigger( 'input' );
  1950. } );
  1951. /**
  1952. * Trigger a search event when the "Try Again" button is clicked.
  1953. *
  1954. * @since 4.9.0
  1955. */
  1956. $document.on( 'click', '.try-again', function( event ) {
  1957. event.preventDefault();
  1958. $pluginInstallSearch.trigger( 'input' );
  1959. } );
  1960. /**
  1961. * Trigger a search event when the search type gets changed.
  1962. *
  1963. * @since 4.6.0
  1964. */
  1965. $( '#typeselector' ).on( 'change', function() {
  1966. var $search = $( 'input[name="s"]' );
  1967. if ( $search.val().length ) {
  1968. $search.trigger( 'input', 'typechange' );
  1969. }
  1970. } );
  1971. /**
  1972. * Click handler for updating a plugin from the details modal on `plugin-install.php`.
  1973. *
  1974. * @since 4.2.0
  1975. *
  1976. * @param {Event} event Event interface.
  1977. */
  1978. $( '#plugin_update_from_iframe' ).on( 'click', function( event ) {
  1979. var target = window.parent === window ? null : window.parent,
  1980. update;
  1981. $.support.postMessage = !! window.postMessage;
  1982. if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'update-core.php' ) ) {
  1983. return;
  1984. }
  1985. event.preventDefault();
  1986. update = {
  1987. action: 'update-plugin',
  1988. data: {
  1989. plugin: $( this ).data( 'plugin' ),
  1990. slug: $( this ).data( 'slug' )
  1991. }
  1992. };
  1993. target.postMessage( JSON.stringify( update ), window.location.origin );
  1994. } );
  1995. /**
  1996. * Click handler for installing a plugin from the details modal on `plugin-install.php`.
  1997. *
  1998. * @since 4.6.0
  1999. *
  2000. * @param {Event} event Event interface.
  2001. */
  2002. $( '#plugin_install_from_iframe' ).on( 'click', function( event ) {
  2003. var target = window.parent === window ? null : window.parent,
  2004. install;
  2005. $.support.postMessage = !! window.postMessage;
  2006. if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'index.php' ) ) {
  2007. return;
  2008. }
  2009. event.preventDefault();
  2010. install = {
  2011. action: 'install-plugin',
  2012. data: {
  2013. slug: $( this ).data( 'slug' )
  2014. }
  2015. };
  2016. target.postMessage( JSON.stringify( install ), window.location.origin );
  2017. } );
  2018. /**
  2019. * Handles postMessage events.
  2020. *
  2021. * @since 4.2.0
  2022. * @since 4.6.0 Switched `update-plugin` action to use the queue.
  2023. *
  2024. * @param {Event} event Event interface.
  2025. */
  2026. $( window ).on( 'message', function( event ) {
  2027. var originalEvent = event.originalEvent,
  2028. expectedOrigin = document.location.protocol + '//' + document.location.host,
  2029. message;
  2030. if ( originalEvent.origin !== expectedOrigin ) {
  2031. return;
  2032. }
  2033. try {
  2034. message = $.parseJSON( originalEvent.data );
  2035. } catch ( e ) {
  2036. return;
  2037. }
  2038. if ( ! message || 'undefined' === typeof message.action ) {
  2039. return;
  2040. }
  2041. switch ( message.action ) {
  2042. // Called from `wp-admin/includes/class-wp-upgrader-skins.php`.
  2043. case 'decrementUpdateCount':
  2044. /** @property {string} message.upgradeType */
  2045. wp.updates.decrementCount( message.upgradeType );
  2046. break;
  2047. case 'install-plugin':
  2048. case 'update-plugin':
  2049. /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
  2050. window.tb_remove();
  2051. /* jscs:enable */
  2052. message.data = wp.updates._addCallbacks( message.data, message.action );
  2053. wp.updates.queue.push( message );
  2054. wp.updates.queueChecker();
  2055. break;
  2056. }
  2057. } );
  2058. /**
  2059. * Adds a callback to display a warning before leaving the page.
  2060. *
  2061. * @since 4.2.0
  2062. */
  2063. $( window ).on( 'beforeunload', wp.updates.beforeunload );
  2064. } );
  2065. })( jQuery, window.wp, window._wpUpdatesSettings );