WordPress.org

Make WordPress Core

Ticket #21170: 21170.6.diff

File 21170.6.diff, 16.1 KB (added by adamsilverstein, 3 years ago)
  • new file src/wp-includes/js/wp-hooks.js

    diff --git src/wp-includes/js/wp-hooks.js src/wp-includes/js/wp-hooks.js
    new file mode 100644
    index 0000000..2d557f7
    - +  
     1( function( wp ) {
     2        'use strict';
     3
     4        /**
     5         * Contains the registered hooks, keyed by hook type. Each hook type is an
     6         * array of objects with priority and callback of each registered hook.
     7         */
     8        var HOOKS = {};
     9
     10        /**
     11         * Returns a function which, when invoked, will add a hook.
     12         *
     13         * @param  {string}   type Type for which hooks are to be added
     14         * @return {Function}      Hook added
     15         */
     16        function createAddHookByType( type ) {
     17                /**
     18                 * Adds the hook to the appropriate hooks container
     19                 *
     20                 * @param {string}   hook     Name of hook to add
     21                 * @param {Function} callback Function to call when the hook is run
     22                 * @param {?number}  priority Priority of this hook (default=10)
     23                 */
     24                return function( hook, callback, priority ) {
     25                        var hookObject, hooks;
     26                        if ( typeof hook !== 'string' || typeof callback !== 'function' ) {
     27                                return;
     28                        }
     29
     30                        // Assign default priority
     31                        if ( 'undefined' === typeof priority ) {
     32                                priority = 10;
     33                        } else {
     34                                priority = parseInt( priority, 10 );
     35                        }
     36
     37                        // Validate numeric priority
     38                        if ( isNaN( priority ) ) {
     39                                return;
     40                        }
     41
     42                        // Check if adding first of type
     43                        if ( ! HOOKS[ type ] ) {
     44                                HOOKS[ type ] = {};
     45                        }
     46
     47                        hookObject = {
     48                                callback: callback,
     49                                priority: priority
     50                        };
     51
     52                        if ( HOOKS[ type ].hasOwnProperty( hook ) ) {
     53                                // Append and re-sort amongst existing
     54                                hooks = HOOKS[ type ][ hook ];
     55                                hooks.push( hookObject );
     56                                hooks = sortHooks( hooks );
     57                        } else {
     58                                // First of its type needs no sort
     59                                hooks = [ hookObject ];
     60                        }
     61
     62                        HOOKS[ type ][ hook ] = hooks;
     63                };
     64        }
     65
     66        /**
     67         * Returns a function which, when invoked, will remove a specified hook.
     68         *
     69         * @param  {string}   type Type for which hooks are to be removed
     70         * @return {Function}      Hook remover
     71         */
     72        function createRemoveHookByType( type ) {
     73                /**
     74                 * Removes the specified hook by resetting its value.
     75                 *
     76                 * @param {string}    hook     Name of hook to remove
     77                 * @param {?Function} callback The specific callback to be removed. If
     78                 *                             omitted, clears all callbacks.
     79                 */
     80                return function( hook, callback ) {
     81                        var handlers, i;
     82
     83                        // Baily early if no hooks exist by this name
     84                        if ( ! HOOKS[ type ] || ! HOOKS[ type ].hasOwnProperty( hook ) ) {
     85                                return;
     86                        }
     87
     88                        if ( callback ) {
     89                                // Try to find specified callback to remove
     90                                handlers = HOOKS[ type ][ hook ];
     91                                for ( i = handlers.length - 1; i >= 0; i-- ) {
     92                                        if ( handlers[ i ].callback === callback ) {
     93                                                handlers.splice( i, 1 );
     94                                        }
     95                                }
     96                        } else {
     97                                // Reset hooks to empty
     98                                delete HOOKS[ type ][ hook ];
     99                        }
     100                };
     101        }
     102
     103        /**
     104         * Returns a function which, when invoked, will execute all registered
     105         * hooks of the specified type by calling upon runner with its hook name
     106         * and arguments.
     107         *
     108         * @param  {string}   type   Type for which hooks are to be run, one of 'action' or 'filter'.
     109         * @param  {Function} runner Function to invoke for each hook callback
     110         * @return {Function}        Hook runner
     111         */
     112        function createRunHookByType( type, runner ) {
     113                /**
     114                 * Runs the specified hook.
     115                 *
     116                 * @param  {string} hook The hook to run
     117                 * @param  {...*}   args Arguments to pass to the action/filter
     118                 * @return {*}           Return value of runner, if applicable
     119                 * @private
     120                 */
     121                return function( /* hook, ...args */ ) {
     122                        var args, hook;
     123
     124                        args = Array.prototype.slice.call( arguments );
     125                        hook = args.shift();
     126
     127                        if ( typeof hook === 'string' ) {
     128                                return runner( hook, args );
     129                        }
     130                };
     131        }
     132
     133        /**
     134         * Performs an action if it exists.
     135         *
     136         * @param {string} action The action to perform.
     137         * @param {...*}   args   Optional args to pass to the action.
     138         * @private
     139         */
     140        function runDoAction( action, args ) {
     141                var handlers, i;
     142                if ( HOOKS.actions ) {
     143                        handlers = HOOKS.actions[ action ];
     144                }
     145
     146                if ( ! handlers ) {
     147                        return;
     148                }
     149
     150                HOOKS.actions.current = action;
     151
     152                for ( i = 0; i < handlers.length; i++ ) {
     153                        handlers[ i ].callback.apply( null, args );
     154                        HOOKS.actions[ action ]['runs'] = HOOKS.actions[ action ]['runs'] ? HOOKS.actions[ action ]['runs'] + 1 : 1;
     155                }
     156        }
     157
     158        /**
     159         * Performs a filter if it exists.
     160         *
     161         * @param  {string} filter The filter to apply.
     162         * @param  {...*}   args   Optional args to pass to the filter.
     163         * @return {*}             The filtered value
     164         * @private
     165         */
     166        function runApplyFilters( filter, args ) {
     167                var handlers, i;
     168                if ( HOOKS.filters ) {
     169                        handlers = HOOKS.filters[ filter ];
     170                }
     171
     172                if ( ! handlers ) {
     173                        return args[ 0 ];
     174                }
     175
     176                HOOKS.filters.current = filter;
     177                HOOKS.filters[ filter ]['runs'] = HOOKS.filters[ filter ]['runs'] ? HOOKS.filters[ filter ]['runs'] + 1 : 1;
     178
     179                for ( i = 0; i < handlers.length; i++ ) {
     180                        args[ 0 ] = handlers[ i ].callback.apply( null, args );
     181                }
     182
     183                return args[ 0 ];
     184        }
     185
     186        /**
     187         * Use an insert sort for keeping our hooks organized based on priority.
     188         *
     189         * @see http://jsperf.com/javascript-sort
     190         *
     191         * @param  {Array} hooks Array of the hooks to sort
     192         * @return {Array}       The sorted array
     193         * @private
     194         */
     195        function sortHooks( hooks ) {
     196                var i, tmpHook, j, prevHook;
     197                for ( i = 1; i < hooks.length; i++ ) {
     198                        tmpHook = hooks[ i ];
     199                        j = i;
     200                        while ( ( prevHook = hooks[ j - 1 ] ) && prevHook.priority > tmpHook.priority ) {
     201                                hooks[ j ] = hooks[ j - 1 ];
     202                                --j;
     203                        }
     204                        hooks[ j ] = tmpHook;
     205                }
     206
     207                return hooks;
     208        }
     209
     210        /**
     211         * Checks to see if an action is currently being executed.
     212         *
     213         * @param  {string} type   Type of hooks to check, one of 'action' or 'filter'.
     214         * @param {string}  action The name of the action to check for, if omitted will check for any action being performed.
     215         *
     216         * @return {[type]}      [description]
     217         */
     218        function createDoingHookByType( type ) {
     219                return function( action ) {
     220
     221                        // If the action was not passed, check for any current hook.
     222                        if ( 'undefined' === typeof action ) {
     223                                return ! 'undefined' === typeof HOOKS[ type ].current;
     224                        }
     225
     226                        // Return the current hook.
     227                        return HOOKS[ type ] && HOOKS[ type ].current ?
     228                                action === HOOKS[ type ].current :
     229                                false;
     230                }
     231        }
     232
     233        /**
     234         * Retrieve the number of times an action is fired.
     235         *
     236         * @param  {string} type   Type for which hooks to check, one of 'action' or 'filter'.
     237         * @param {string}  action The action to check.
     238         *
     239         * @return {[type]}      [description]
     240         */
     241        function createDidHookByType( type ) {
     242                return function( action ) {
     243                        return HOOKS[ type ] && HOOKS[ type ][ action ] && HOOKS[ type ][ action ]['runs'] ?
     244                                HOOKS[ type ][ action ]['runs'] :
     245                                0;
     246                }
     247        }
     248
     249        /**
     250         * Check to see if an action is registered for a hook.
     251         *
     252         * @param  {string} type   Type for which hooks to check, one of 'action' or 'filter'.
     253         * @param {string}  action  The action to check.
     254         *
     255         * @return {bool}      Whether an action has been registered for a hook.
     256         */
     257        function createHasHookByType( type ) {
     258                return function( action ) {
     259                        return HOOKS[ type ] && HOOKS[ type ][ action ] ?
     260                                !! HOOKS[ type ][ action ] :
     261                                false;
     262                }
     263        }
     264
     265        wp.hooks = {
     266                removeFilter: createRemoveHookByType( 'filters' ),
     267                applyFilters: createRunHookByType( 'filters', runApplyFilters ),
     268                addFilter: createAddHookByType( 'filters' ),
     269                removeAction: createRemoveHookByType( 'actions' ),
     270                doAction: createRunHookByType( 'actions', runDoAction ),
     271                addAction: createAddHookByType( 'actions' ),
     272                doingAction: createDoingHookByType( 'actions' ),
     273                didAction: createDidHookByType( 'actions' ),
     274                hasAction: createHasHookByType( 'actions' ),
     275        };
     276} )( window.wp = window.wp || {} );
  • src/wp-includes/plugin.php

    diff --git src/wp-includes/plugin.php src/wp-includes/plugin.php
    index 86f1c3b..86f9db8 100644
    function doing_filter( $filter = null ) { 
    363363}
    364364
    365365/**
    366  * Retrieve the name of an action currently being processed.
     366 * Retrieve whether action currently being processed.
    367367 *
    368368 * @since 3.9.0
    369369 *
  • src/wp-includes/script-loader.php

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index def438c..4916c8f 100644
    function wp_default_scripts( &$scripts ) { 
    8585
    8686        $scripts->add( 'wp-a11y', "/wp-includes/js/wp-a11y$suffix.js", array( 'jquery' ), false, 1 );
    8787
     88        $scripts->add( 'wp-hooks', "/wp-includes/js/wp-hooks$suffix.js", array(), false, 1 );
     89
    8890        $scripts->add( 'sack', "/wp-includes/js/tw-sack$suffix.js", array(), '1.6.1', 1 );
    8991
    9092        $scripts->add( 'quicktags', "/wp-includes/js/quicktags$suffix.js", array(), false, 1 );
  • tests/qunit/index.html

    diff --git tests/qunit/index.html tests/qunit/index.html
    index 0c9d820..fca4114 100644
     
    5050                <script src="../../src/wp-includes/js/customize-base.js"></script>
    5151                <script src="../../src/wp-includes/js/customize-models.js"></script>
    5252                <script src="../../src/wp-includes/js/shortcode.js"></script>
     53                <script src="../../src/wp-includes/js/wp-hooks.js"></script>
    5354                <script src="../../src/wp-admin/js/customize-controls.js"></script>
    5455                <script src="../../src/wp-includes/js/wp-api.js"></script>
    5556
     
    7071                <script src="wp-admin/js/customize-base.js"></script>
    7172                <script src="wp-admin/js/customize-header.js"></script>
    7273                <script src="wp-includes/js/shortcode.js"></script>
     74                <script src="wp-includes/js/wp-hooks.js"></script>
    7375                <script src="wp-includes/js/wp-api.js"></script>
    7476                <script src="wp-admin/js/customize-controls.js"></script>
    7577                <script src="wp-admin/js/customize-controls-utils.js"></script>
  • new file tests/qunit/wp-includes/js/wp-hooks.js

    diff --git tests/qunit/wp-includes/js/wp-hooks.js tests/qunit/wp-includes/js/wp-hooks.js
    new file mode 100644
    index 0000000..248c466
    - +  
     1/* global wp */
     2( function( QUnit ) {
     3        QUnit.module( 'wp-hooks' );
     4
     5        function filter_a( str ) {
     6                return str + 'a';
     7        }
     8        function filter_b( str ) {
     9                return str + 'b';
     10        }
     11        function filter_c( str ) {
     12                return str + 'c';
     13        }
     14        function action_a() {
     15                window.actionValue += 'a';
     16        }
     17        function action_b() {
     18                window.actionValue += 'b';
     19        }
     20        function action_c() {
     21                window.actionValue += 'c';
     22        }
     23        window.actionValue = '';
     24
     25        QUnit.test( 'add and remove a filter', function() {
     26                expect( 1 );
     27                wp.hooks.addFilter( 'test.filter', filter_a );
     28                wp.hooks.removeFilter( 'test.filter' );
     29                equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'test' );
     30        } );
     31
     32        QUnit.test( 'add a filter and run it', function() {
     33                expect( 1 );
     34                wp.hooks.addFilter( 'test.filter', filter_a );
     35                equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testa' );
     36                wp.hooks.removeFilter( 'test.filter' );
     37        } );
     38
     39        QUnit.test( 'add 2 filters in a row and run them', function() {
     40                expect( 1 );
     41                wp.hooks.addFilter( 'test.filter', filter_a );
     42                wp.hooks.addFilter( 'test.filter', filter_b );
     43                equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testab' );
     44                wp.hooks.removeFilter( 'test.filter' );
     45        } );
     46
     47        QUnit.test( 'add 3 filters with different priorities and run them', function() {
     48                expect( 1 );
     49                wp.hooks.addFilter( 'test.filter', filter_a );
     50                wp.hooks.addFilter( 'test.filter', filter_b, 2 );
     51                wp.hooks.addFilter( 'test.filter', filter_c, 8 );
     52                equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testbca' );
     53                wp.hooks.removeFilter( 'test.filter' );
     54        } );
     55
     56        QUnit.test( 'add and remove an action', function() {
     57                expect( 1 );
     58                window.actionValue = '';
     59                wp.hooks.addAction( 'test.action', action_a );
     60                wp.hooks.removeAction( 'test.action' );
     61                wp.hooks.doAction( 'test.action' );
     62                equal( window.actionValue, '' );
     63        } );
     64
     65        QUnit.test( 'add an action and run it', function() {
     66                expect( 1 );
     67                window.actionValue = '';
     68                wp.hooks.addAction( 'test.action', action_a );
     69                wp.hooks.doAction( 'test.action' );
     70                equal( window.actionValue, 'a' );
     71                wp.hooks.removeAction( 'test.action' );
     72        } );
     73
     74        QUnit.test( 'add 2 actions in a row and then run them', function() {
     75                expect( 1 );
     76                window.actionValue = '';
     77                wp.hooks.addAction( 'test.action', action_a );
     78                wp.hooks.addAction( 'test.action', action_b );
     79                wp.hooks.doAction( 'test.action' );
     80                equal( window.actionValue, 'ab' );
     81                wp.hooks.removeAction( 'test.action' );
     82        } );
     83
     84        QUnit.test( 'add 3 actions with different priorities and run them', function() {
     85                expect( 1 );
     86                window.actionValue = '';
     87                wp.hooks.addAction( 'test.action', action_a );
     88                wp.hooks.addAction( 'test.action', action_b, 2 );
     89                wp.hooks.addAction( 'test.action', action_c, 8 );
     90                wp.hooks.doAction( 'test.action' );
     91                equal( window.actionValue, 'bca' );
     92                wp.hooks.removeAction( 'test.action' );
     93        } );
     94
     95        QUnit.test( 'pass in two arguments to an action', function() {
     96                var arg1 = 10,
     97                        arg2 = 20;
     98
     99                expect( 4 );
     100
     101                wp.hooks.addAction( 'test.action', function( a, b ) {
     102                        equal( arg1, a );
     103                        equal( arg2, b );
     104                } );
     105                wp.hooks.doAction( 'test.action', arg1, arg2 );
     106                wp.hooks.removeAction( 'test.action' );
     107
     108                equal( arg1, 10 );
     109                equal( arg2, 20 );
     110        } );
     111
     112        QUnit.test( 'fire action multiple times', function() {
     113                var func;
     114                expect( 2 );
     115
     116                func = function() {
     117                        ok( true );
     118                };
     119
     120                wp.hooks.addAction( 'test.action', func );
     121                wp.hooks.doAction( 'test.action' );
     122                wp.hooks.doAction( 'test.action' );
     123                wp.hooks.removeAction( 'test.action' );
     124        } );
     125
     126        QUnit.test( 'remove specific action callback', function() {
     127                window.actionValue = '';
     128                wp.hooks.addAction( 'test.action', action_a );
     129                wp.hooks.addAction( 'test.action', action_b, 2 );
     130                wp.hooks.addAction( 'test.action', action_c, 8 );
     131
     132                wp.hooks.removeAction( 'test.action', action_b );
     133                wp.hooks.doAction( 'test.action' );
     134                equal( window.actionValue, 'ca' );
     135                wp.hooks.removeAction( 'test.action' );
     136        } );
     137
     138        QUnit.test( 'remove specific filter callback', function() {
     139                wp.hooks.addFilter( 'test.filter', filter_a );
     140                wp.hooks.addFilter( 'test.filter', filter_b, 2 );
     141                wp.hooks.addFilter( 'test.filter', filter_c, 8 );
     142
     143                wp.hooks.removeFilter( 'test.filter', filter_b );
     144                equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testca' );
     145                wp.hooks.removeFilter( 'test.filter' );
     146        } );
     147
     148        // Test doingAction, didAction, hasAction.
     149        QUnit.test( 'Test doingAction, didAction and hasAction.', function() {
     150
     151                // Reset state for testing.
     152                wp.hooks.removeAction( 'test.action' );
     153                wp.hooks.addAction( 'another.action', function(){} );
     154                wp.hooks.doAction( 'another.action' );
     155
     156                // Verify no action is running yet.
     157                notOk( wp.hooks.doingAction( 'test.action' ), 'The test.action is not running.' );
     158                equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' );
     159                notOk( wp.hooks.hasAction( 'test.action' ), 'The test.action is not registered.' );
     160
     161                wp.hooks.addAction( 'test.action', action_a );
     162
     163                // Verify action added, not running yet.
     164                notOk( wp.hooks.doingAction( 'test.action' ), 'The test.action is not running.' );
     165                equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' );
     166                ok( wp.hooks.hasAction( 'test.action' ), 'The test.action is registered.' );
     167
     168                wp.hooks.doAction( 'test.action' );
     169
     170                // Verify action added and running.
     171                ok( wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' );
     172                equal( wp.hooks.didAction( 'test.action' ), 1, 'The test.action has run once.' );
     173                ok( wp.hooks.hasAction( 'test.action' ), 'The test.action is registered.' );
     174
     175                wp.hooks.doAction( 'test.action' );
     176                equal( wp.hooks.didAction( 'test.action' ), 2, 'The test.action has run twice.' );
     177
     178                wp.hooks.removeAction( 'test.action' );
     179
     180                // Verify state is reset appropriately.
     181                ok( wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' );
     182                equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' );
     183                notOk( wp.hooks.hasAction( 'test.action' ), 'The test.action is not registered.' );
     184
     185                wp.hooks.doAction( 'another.action' );
     186                notOk( wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' );
     187
     188
     189        } );
     190
     191} )( window.QUnit );