Passed
Pull Request — trunk (#1007)
by
unknown
11:05
created

CMB2_Hook_Finder   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 269
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 60
eloc 160
c 1
b 0
f 0
dl 0
loc 269
rs 3.6

4 Methods

Rating   Name   Duplication   Size   Complexity  
B get_files() 0 30 11
C wp_list_filter() 0 28 13
F process_hooks() 0 167 31
A get_hook_link() 0 16 5

How to fix   Complexity   

Complex Class

Complex classes like CMB2_Hook_Finder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CMB2_Hook_Finder, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Generate documentation for hooks in CMB2
4
 * Credit: https://github.com/woothemes/woocommerce/blob/master/apigen/hook-docs.php
5
 */
6
class CMB2_Hook_Finder {
7
	private static $current_file           = '';
8
	private static $files_to_scan          = array();
9
	private static $pattern_custom_actions = '/do_action(.*?);/i';
0 ignored issues
show
introduced by
The private property $pattern_custom_actions is not used, and could be removed.
Loading history...
10
	private static $pattern_custom_filters = '/apply_filters(.*?);/i';
0 ignored issues
show
introduced by
The private property $pattern_custom_filters is not used, and could be removed.
Loading history...
11
	private static $found_files            = array();
12
	private static $custom_hooks_found     = '';
13
14
	private static function get_files( $pattern, $flags = 0, $path = '' ) {
15
16
	    if ( ! $path && ( $dir = dirname( $pattern ) ) != '.' ) {
17
18
	        if ($dir == '\\' || $dir == '/') { $dir = ''; } // End IF Statement
19
20
	        return self::get_files(basename( $pattern ), $flags, $dir . '/' );
21
22
	    } // End IF Statement
23
	    $paths = glob( $path . '*', GLOB_ONLYDIR | GLOB_NOSORT );
24
	    $files = glob( $path . $pattern, $flags );
25
26
	    if ( is_array( $paths ) ) {
0 ignored issues
show
introduced by
The condition is_array($paths) is always true.
Loading history...
27
		    foreach ( $paths as $p ) {
28
			    $found_files = array();
29
		   		$retrieved_files = (array) self::get_files( $pattern, $flags, $p . '/' );
30
		   		foreach ( $retrieved_files as $file ) {
31
			   		if ( ! in_array( $file, self::$found_files ) )
32
			   			$found_files[] = $file;
33
		   		}
34
35
		   		self::$found_files = array_merge( self::$found_files, $found_files );
36
37
		   		if ( is_array( $files ) && is_array( $found_files ) ) {
38
		   			$files = array_merge( $files, $found_files );
39
		   		}
40
41
		    } // End FOREACH Loop
42
	    }
43
	    return $files;
44
    }
45
46
	private static function get_hook_link( $hook, $details = array() ) {
47
		if ( ! empty( $details['class'] ) ) {
48
			$link = 'https://cmb2.io/api//source-class-' . $details['class'] . '.html#' . $details['line'];
49
		} elseif ( ! empty( $details['function'] ) ) {
50
			$link = 'https://cmb2.io/api//source-function-' . $details['function'] . '.html#' . $details['line'];
51
		} else {
52
			$link = 'https://github.com/CMB2/CMB2/search?utf8=%E2%9C%93&q=' . $hook;
53
		}
54
55
		if ( false !== strpos( $hook, '{' ) || false !== strpos( $hook, '$' ) ) {
56
			$hook = '"'. $hook .'"';
57
		} else {
58
			$hook = "'$hook'";
59
		}
60
61
		return '<a href="' . $link . '">' . $hook . '</a>';
62
	}
63
64
	public static function process_hooks() {
65
		// If we have one, get the PHP files from it.
66
		$class_files = self::get_files( '*.php', GLOB_MARK, dirname( __FILE__ ) . '/../includes/' );
67
		$class_files[] = dirname( __FILE__ ) . '/../init.php';
68
69
		self::$files_to_scan = array(
70
			'Hooks' => $class_files,
71
		);
72
73
		$scanned = array();
74
75
		ob_start();
76
77
		echo '<div id="content">';
78
		echo '<h1>WordPress Action and Filter Hook Reference</h1>';
79
80
		foreach ( self::$files_to_scan as $heading => $files ) {
81
			self::$custom_hooks_found = array();
82
83
			foreach ( $files as $f ) {
84
				self::$current_file = basename( $f );
85
				$tokens             = token_get_all( file_get_contents( $f ) );
86
				$token_type         = false;
87
				$current_class      = '';
88
				$current_function   = '';
89
90
				if ( in_array( self::$current_file, $scanned ) ) {
91
					continue;
92
				}
93
94
				$scanned[] = self::$current_file;
95
96
				foreach ( $tokens as $index => $token ) {
97
					if ( is_array( $token ) ) {
98
						if ( $token[0] == T_CLASS ) {
99
							$token_type = 'class';
100
						} elseif ( $token[0] == T_FUNCTION ) {
101
							$token_type = 'function';
102
						} elseif ( $token[1] === 'do_action' ) {
103
							$token_type = 'action';
104
						} elseif ( $token[1] === 'apply_filters' ) {
105
							$token_type = 'filter';
106
						} elseif ( $token_type && ! empty( trim( $token[1] ) ) ) {
107
							switch ( $token_type ) {
108
								case 'class' :
109
									$current_class = $token[1];
110
								break;
111
								case 'function' :
112
									$current_function = $token[1];
113
								break;
114
								case 'filter' :
115
								case 'action' :
116
									$hook = trim( $token[1], "'" );
117
									$loop = 0;
118
119
									if ( '_' === substr( $hook, '-1', 1 ) ) {
120
										$hook .= '{';
121
										$open = true;
122
										// Keep adding to hook until we find a comma or colon
123
										while ( 1 ) {
124
											$loop ++;
125
											$next_hook  = trim( trim( is_string( $tokens[ $index + $loop ] ) ? $tokens[ $index + $loop ] : $tokens[ $index + $loop ][1], '"' ), "'" );
126
127
											if ( in_array( $next_hook, array( '.', '{', '}', '"', "'", ' ' ) ) ) {
128
												continue;
129
											}
130
131
											$hook_first = substr( $next_hook, 0, 1 );
132
											$hook_last  = substr( $next_hook, -1, 1 );
133
134
											if ( in_array( $next_hook, array( ',', ';' ), true ) ) {
135
												if ( $open ) {
136
													$hook .= '}';
137
													$open = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $open is dead and can be removed.
Loading history...
138
												}
139
												break;
140
											}
141
142
											if ( '_' === $hook_first ) {
143
												// Because CMB2 uses an _id() method
144
												if ( '_id' !== $next_hook ) {
145
													$next_hook = '}' . $next_hook;
146
													$open = false;
147
												}
148
											}
149
150
											if ( '_' === $hook_last ) {
151
												$next_hook .= '{';
152
												$open = true;
153
											}
154
155
											$hook .= $next_hook;
156
											// echo '<xmp>$hook: '. print_r( $hook, true ) .'</xmp>';
157
										}
158
									}
159
160
									if ( isset( self::$custom_hooks_found[ $hook ] ) ) {
161
										self::$custom_hooks_found[ $hook ]['file'][] = self::$current_file;
162
									} else {
163
    									self::$custom_hooks_found[ $hook ] = array(
164
											'line'     => $token[2],
165
											'class'    => $current_class,
166
											'function' => $current_function,
167
											'file'     => array( self::$current_file ),
168
											'type'     => $token_type
169
										);
170
									}
171
								break;
172
							}
173
							$token_type = false;
174
						}
175
					}
176
				}
177
			}
178
			// die( '<xmp>self::$custom_hooks_found: '. print_r( self::$custom_hooks_found, true ) .'</xmp>' );
179
180
			foreach ( self::$custom_hooks_found as $hook => $details ) {
181
				if ( ! strstr( $hook, 'cmb2' ) ) {
182
					unset( self::$custom_hooks_found[ $hook ] );
183
				}
184
			}
185
186
			ksort( self::$custom_hooks_found );
187
188
			if ( ! empty( self::$custom_hooks_found ) ) {
189
				$actions = self::wp_list_filter( self::$custom_hooks_found, array( 'type' => 'action' ) );
190
				$filters = self::wp_list_filter( self::$custom_hooks_found, array( 'type' => 'filter' ) );
191
192
				echo '<div class="panel panel-default"><div class="panel-heading"><h2>Action Hooks</h2></div>';
193
194
				echo '<table class="summary table table-bordered table-striped"><thead><tr><th>Hook</th><th>File(s)</th></tr></thead><tbody>';
195
196
				foreach ( $actions as $hook => $details ) {
197
					echo '<tr>
198
						<td>' . self::get_hook_link( $hook, $details ) . '</td>
199
						<td>' . implode( ', ', array_unique( $details['file'] ) ) . '</td>
200
					</tr>' . "\n";
201
				}
202
203
				echo '</tbody></table></div>';
204
				echo '<div class="panel panel-default"><div class="panel-heading"><h2>Filter Hooks</h2></div>';
205
206
				echo '<table class="summary table table-bordered table-striped"><thead><tr><th>Hook</th><th>File(s)</th></tr></thead><tbody>';
207
208
				foreach ( $filters as $hook => $details ) {
209
					echo '<tr>
210
						<td>' . self::get_hook_link( $hook, $details ) . '</td>
211
						<td>' . implode( ', ', array_unique( $details['file'] ) ) . '</td>
212
					</tr>' . "\n";
213
				}
214
215
				echo '</tbody></table></div>';
216
			}
217
		}
218
219
		echo '</div><div id="footer">';
220
221
		$html   = file_get_contents( '/Users/JT/Sites/wpengine/api/tree.html' );
222
		$header = explode( '<div id="content">', $html );
223
		$header = str_replace( '<li class="active">', '<li>', current( $header ) );
224
		$header = str_replace( '<li class="hooks">', '<li class="active">', $header );
225
		$header = str_replace( '<li class="hooks">', '<li class="active">', $header );
226
		$header = str_replace( 'Tree | ', 'Hook Reference | ', $header );
227
		$footer = explode( '<div id="footer">', $html );
228
229
		file_put_contents( '/Users/JT/Sites/wpengine/api/hook-docs.html', $header . ob_get_clean() . end( $footer ) );
230
		echo "Hook docs generated :)\n";
231
	}
232
233
	/**
234
	 * Filters a list of objects, based on a set of key => value arguments.
235
	 *
236
	 * @since 3.1.0
237
	 *
238
	 * @param array  $list     An array of objects to filter.
239
	 * @param array  $args     Optional. An array of key => value arguments to match
240
	 *                         against each object. Default empty array.
241
	 * @param string $operator Optional. The logical operation to perform. 'AND' means
242
	 *                         all elements from the array must match. 'OR' means only
243
	 *                         one element needs to match. 'NOT' means no elements may
244
	 *                         match. Default 'AND'.
245
	 * @return array Array of found values.
246
	 */
247
	protected static function wp_list_filter( $list, $args = array(), $operator = 'AND' ) {
248
		if ( ! is_array( $list ) )
0 ignored issues
show
introduced by
The condition is_array($list) is always true.
Loading history...
249
			return array();
250
251
		if ( empty( $args ) )
252
			return $list;
253
254
		$operator = strtoupper( $operator );
255
		$count = count( $args );
256
		$filtered = array();
257
258
		foreach ( $list as $key => $obj ) {
259
			$to_match = (array) $obj;
260
261
			$matched = 0;
262
			foreach ( $args as $m_key => $m_value ) {
263
				if ( array_key_exists( $m_key, $to_match ) && $m_value == $to_match[ $m_key ] )
264
					$matched++;
265
			}
266
267
			if ( ( 'AND' == $operator && $matched == $count )
268
			  || ( 'OR' == $operator && $matched > 0 )
269
			  || ( 'NOT' == $operator && 0 == $matched ) ) {
270
				$filtered[$key] = $obj;
271
			}
272
		}
273
274
		return $filtered;
275
	}
276
}
277
278
CMB2_Hook_Finder::process_hooks();
279
280