Completed
Push — master ( 144c08...e73d65 )
by J.D.
02:53
created

WordPoints_Hook_Router::fire_event()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 33
rs 8.439
cc 6
eloc 17
nc 5
nop 3
1
<?php
2
3
/**
4
 * Hook action router class.
5
 *
6
 * @package wordpoints-hooks-api
7
 * @since 1.0.0
8
 */
9
10
/**
11
 * Routes WordPress actions to WordPoints hook actions, and finally to hook events.
12
 *
13
 * Each WordPress action can have several different WordPoints hook actions hooked to
14
 * it. This router handles hooking into the WordPress action, and making sure the
15
 * hook actions are processed when it is fired. This allows us to hook to each action
16
 * once, even if multiple hook actions are registered for it.
17
 *
18
 * When a hook action is fired, the router then loops through the events which are
19
 * registered to fire on that hook action, and fires each of them.
20
 *
21
 * This arrangement allows for events and actions to be decoupled from WordPress
22
 * actions, and from each other as well. As a result, action classes don't have to
23
 * be loaded until the router is called on the action that they are attached to. The
24
 * event classes can be lazy-loaded as well.
25
 *
26
 * It also makes it possible for a hook action to abort firing any events if it
27
 * chooses to do so.
28
 *
29
 * @since 1.0.0
30
 */
31
class WordPoints_Hook_Router {
32
33
	/**
34
	 * The hooks app object.
35
	 *
36
	 * @since 1.0.0
37
	 *
38
	 * @var WordPoints_Hooks
39
	 */
40
	protected $hooks;
41
	
42
	/**
43
	 * The actions registry object.
44
	 *
45
	 * @since 1.0.0
46
	 *
47
	 * @var WordPoints_Hook_Actions
48
	 */
49
	protected $actions;
50
51
	/**
52
	 * The events registry object.
53
	 *
54
	 * @since 1.0.0
55
	 *
56
	 * @var WordPoints_Hook_Events
57
	 */
58
	protected $events;
59
60
	/**
61
	 * The actions, indexed by WordPress action/filter hooks.
62
	 *
63
	 * The indexes are of this format: "$action_or_filter_name,$priority".
64
	 *
65
	 * @since 1.0.0
66
	 *
67
	 * @var array
68
	 */
69
	protected $action_index = array();
70
71
	/**
72
	 * The events, indexed by action slug and action type.
73
	 *
74
	 * @since 1.0.0
75
	 *
76
	 * @var array
77
	 */
78
	protected $event_index = array();
79
80
	/**
81
	 * @since 1.0.0
82
	 */
83
	public function __call( $name, $args ) {
84
85
		$this->route_action( $name, $args );
86
87
		// Return the first value, in case it is hooked to a filter.
88
		$return = null;
89
		if ( isset( $args[0] ) ) {
90
			$return = $args[0];
91
		}
92
93
		return $return;
94
	}
95
96
	/**
97
	 * Routes a WordPress action to WordPoints hook actions, and fires their events.
98
	 *
99
	 * @since 1.0.0
100
	 *
101
	 * @param string $name The action ID. This is not the slug of a hook action, but
102
	 *                     rather a unique ID for the WordPress action based on the
103
	 *                     action name and the priority.
104
	 * @param array  $args The args the action was fired with.
105
	 */
106
	protected function route_action( $name, $args ) {
107
108
		if ( ! isset( $this->action_index[ $name ] ) ) {
109
			return;
110
		}
111
112
		// We might normally do this in the constructor, however, the events
113
		// registry attempts to access the router in its own constructor. The result
114
		// of attempting to do this before the router itself has been fully
115
		// constructed is that the events registry gets null instead of the router.
116
		if ( ! isset( $this->hooks ) ) {
117
118
			$hooks = wordpoints_hooks();
119
120
			$this->hooks   = $hooks;
121
			$this->events  = $hooks->events;
122
			$this->actions = $hooks->actions;
123
		}
124
125
		foreach ( $this->action_index[ $name ]['actions'] as $slug => $data ) {
126
127
			if ( ! isset( $this->event_index[ $slug ] ) ) {
128
				continue;
129
			}
130
131
			$action_object = $this->actions->get( $slug, $args, $data );
132
133
			if ( ! ( $action_object instanceof WordPoints_Hook_ActionI ) ) {
134
				continue;
135
			}
136
137
			if ( ! $action_object->should_fire() ) {
138
				continue;
139
			}
140
141
			foreach ( $this->event_index[ $slug ] as $type => $events ) {
142
				foreach ( $events as $event_slug => $unused ) {
143
144
					if ( ! $this->events->is_registered( $event_slug ) ) {
145
						continue;
146
					}
147
148
					$event_args = $this->events->args->get_children( $event_slug, array( $action_object ) );
149
150
					if ( empty( $event_args ) ) {
151
						continue;
152
					}
153
154
					$event_args = new WordPoints_Hook_Event_Args( $event_args );
155
156
					$this->hooks->fire( $type, $event_slug, $event_args );
157
				}
158
			}
159
		}
160
	}
161
162
	/**
163
	 * Register an action with the router.
164
	 *
165
	 * The arg number will be automatically determined based on $data['arg_index']
166
	 * and $data['requirements']. So in most cases $arg_number may be omitted.
167
	 *
168
	 * @since 1.0.0
169
	 *
170
	 * @param string $slug The slug of the action.
171
	 * @param array  $args {
172
	 *        Other arguments.
173
	 *
174
	 *        @type string $action     The name of the WordPress action for this hook action.
175
	 *        @type int    $priority   The priority for the WordPress action. Default: 10.
176
	 *        @type int    $arg_number The number of args the action object expects. Default: 1.
177
	 *        @type array  $data {
178
	 *              Args that will be passed to the action object's constructor.
179
	 *
180
	 *              @type int[] $arg_index    List of args (starting from 0), indexed by slug.
181
	 *              @type array $requirements List of requirements, indexed by arg index (from 0).
182
	 *        }
183
	 * }
184
	 */
185
	public function add_action( $slug, array $args ) {
186
187
		$priority = 10;
188
		if ( isset( $args['priority'] ) ) {
189
			$priority = $args['priority'];
190
		}
191
192
		if ( ! isset( $args['action'] ) ) {
193
			return;
194
		}
195
196
		$method = "{$args['action']},{$priority}";
197
198
		$this->action_index[ $method ]['actions'][ $slug ] = array();
199
200
		$arg_number = 1;
201
202
		if ( isset( $args['data'] ) ) {
203
204
			if ( isset( $args['data']['arg_index'] ) ) {
205
				$arg_number = 1 + max( $args['data']['arg_index'] );
206
			}
207
208
			if ( isset( $args['data']['requirements'] ) ) {
209
				$requirements = 1 + max( array_keys( $args['data']['requirements'] ) );
210
211
				if ( $requirements > $arg_number ) {
212
					$arg_number = $requirements;
213
				}
214
			}
215
216
			$this->action_index[ $method ]['actions'][ $slug ] = $args['data'];
217
		}
218
219
		if ( isset( $args['arg_number'] ) ) {
220
			$arg_number = $args['arg_number'];
221
		}
222
223
		// If this action is already being routed, and will have enough args, we
224
		// don't need to hook to it again.
225
		if (
226
			isset( $this->action_index[ $method ]['arg_number'] )
227
			&& $this->action_index[ $method ]['arg_number'] >= $arg_number
228
		) {
229
			return;
230
		}
231
232
		$this->action_index[ $method ]['arg_number'] = $arg_number;
233
234
		add_action( $args['action'], array( $this, $method ), $priority, $arg_number );
235
	}
236
237
	/**
238
	 * Deregister an action with the router.
239
	 *
240
	 * @since 1.0.0
241
	 *
242
	 * @param string $slug The action slug.
243
	 */
244
	public function remove_action( $slug ) {
245
246
		foreach ( $this->action_index as $method => $data ) {
247
			if ( isset( $data['actions'][ $slug ] ) ) {
248
249
				unset( $this->action_index[ $method ]['actions'][ $slug ] );
250
251
				if ( empty( $this->action_index[ $method ]['actions'] ) ) {
252
253
					unset( $this->action_index[ $method ] );
254
255
					list( $action, $priority ) = explode( ',', $method );
256
257
					remove_action( $action, array( $this, $method ), $priority );
258
				}
259
			}
260
		}
261
	}
262
263
	/**
264
	 * Hook an event to an action.
265
	 *
266
	 * @since 1.0.0
267
	 *
268
	 * @param string $event_slug The slug of the event.
269
	 * @param string $action_slug The slug of the action.
270
	 * @param string $action_type The type of action. Default is 'fire'.
271
	 */
272
	public function add_event_to_action( $event_slug, $action_slug, $action_type = 'fire' ) {
273
		$this->event_index[ $action_slug ][ $action_type ][ $event_slug ] = true;
274
	}
275
276
	/**
277
	 * Unhook an event from an action.
278
	 *
279
	 * @since 1.0.0
280
	 *
281
	 * @param string $event_slug  The slug of the event.
282
	 * @param string $action_slug The slug of the action.
283
	 * @param string $action_type The type of action. Default is 'fire'.
284
	 */
285
	public function remove_event_from_action( $event_slug, $action_slug, $action_type = 'fire' ) {
286
		unset( $this->event_index[ $action_slug ][ $action_type ][ $event_slug ] );
287
	}
288
289
	/**
290
	 * Get the event index.
291
	 *
292
	 * @since 1.0.0
293
	 *
294
	 * @return array[] The event index.
295
	 */
296
	public function get_event_index() {
297
298
		if ( empty( $this->event_index ) ) {
299
			wordpoints_hooks()->events;
300
		}
301
302
		return $this->event_index;
303
	}
304
}
305
306
// EOF
307