| Total Complexity | 43 |
| Total Lines | 338 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like PucFactory 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 PucFactory, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 21 | class PucFactory { |
||
| 22 | protected static $classVersions = array(); |
||
| 23 | protected static $sorted = false; |
||
| 24 | |||
| 25 | protected static $myMajorVersion = ''; |
||
| 26 | protected static $latestCompatibleVersion = ''; |
||
| 27 | |||
| 28 | /** |
||
| 29 | * A wrapper method for buildUpdateChecker() that reads the metadata URL from the plugin or theme header. |
||
| 30 | * |
||
| 31 | * @param string $fullPath Full path to the main plugin file or the theme's style.css. |
||
| 32 | * @param array $args Optional arguments. Keys should match the argument names of the buildUpdateChecker() method. |
||
| 33 | * @return Plugin\UpdateChecker|Theme\UpdateChecker|Vcs\BaseChecker |
||
| 34 | */ |
||
| 35 | public static function buildFromHeader($fullPath, $args = array()) { |
||
| 36 | $fullPath = self::normalizePath($fullPath); |
||
| 37 | |||
| 38 | //Set up defaults. |
||
| 39 | $defaults = array( |
||
| 40 | 'metadataUrl' => '', |
||
| 41 | 'slug' => '', |
||
| 42 | 'checkPeriod' => 12, |
||
| 43 | 'optionName' => '', |
||
| 44 | 'muPluginFile' => '', |
||
| 45 | ); |
||
| 46 | $args = array_merge($defaults, array_intersect_key($args, $defaults)); |
||
| 47 | extract($args, EXTR_SKIP); |
||
| 48 | |||
| 49 | //Check for the service URI |
||
| 50 | if ( empty($metadataUrl) ) { |
||
| 51 | $metadataUrl = self::getServiceURI($fullPath); |
||
| 52 | } |
||
| 53 | |||
| 54 | return self::buildUpdateChecker($metadataUrl, $fullPath, $slug, $checkPeriod, $optionName, $muPluginFile); |
||
| 55 | } |
||
| 56 | |||
| 57 | /** |
||
| 58 | * Create a new instance of the update checker. |
||
| 59 | * |
||
| 60 | * This method automatically detects if you're using it for a plugin or a theme and chooses |
||
| 61 | * the appropriate implementation for your update source (JSON file, GitHub, BitBucket, etc). |
||
| 62 | * |
||
| 63 | * @see UpdateChecker::__construct |
||
| 64 | * |
||
| 65 | * @param string $metadataUrl The URL of the metadata file, a GitHub repository, or another supported update source. |
||
| 66 | * @param string $fullPath Full path to the main plugin file or to the theme directory. |
||
| 67 | * @param string $slug Custom slug. Defaults to the name of the main plugin file or the theme directory. |
||
| 68 | * @param int $checkPeriod How often to check for updates (in hours). |
||
| 69 | * @param string $optionName Where to store bookkeeping info about update checks. |
||
| 70 | * @param string $muPluginFile The plugin filename relative to the mu-plugins directory. |
||
| 71 | * @return Plugin\UpdateChecker|Theme\UpdateChecker|Vcs\BaseChecker |
||
| 72 | */ |
||
| 73 | public static function buildUpdateChecker($metadataUrl, $fullPath, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') { |
||
| 74 | $fullPath = self::normalizePath($fullPath); |
||
| 75 | $id = null; |
||
| 76 | |||
| 77 | //Plugin or theme? |
||
| 78 | $themeDirectory = self::getThemeDirectoryName($fullPath); |
||
| 79 | if ( self::isPluginFile($fullPath) ) { |
||
| 80 | $type = 'Plugin'; |
||
| 81 | $id = $fullPath; |
||
| 82 | } else if ( $themeDirectory !== null ) { |
||
| 83 | $type = 'Theme'; |
||
| 84 | $id = $themeDirectory; |
||
| 85 | } else { |
||
| 86 | throw new \RuntimeException(sprintf( |
||
| 87 | 'The update checker cannot determine if "%s" is a plugin or a theme. ' . |
||
| 88 | 'This is a bug. Please contact the PUC developer.', |
||
| 89 | htmlentities($fullPath) |
||
| 90 | )); |
||
| 91 | } |
||
| 92 | |||
| 93 | //Which hosting service does the URL point to? |
||
| 94 | $service = self::getVcsService($metadataUrl); |
||
| 95 | |||
| 96 | $apiClass = null; |
||
| 97 | if ( empty($service) ) { |
||
| 98 | //The default is to get update information from a remote JSON file. |
||
| 99 | $checkerClass = $type . '\\UpdateChecker'; |
||
| 100 | } else { |
||
| 101 | //You can also use a VCS repository like GitHub. |
||
| 102 | $checkerClass = 'Vcs\\' . $type . 'UpdateChecker'; |
||
| 103 | $apiClass = $service . 'Api'; |
||
| 104 | } |
||
| 105 | |||
| 106 | $checkerClass = self::getCompatibleClassVersion($checkerClass); |
||
| 107 | if ( $checkerClass === null ) { |
||
| 108 | //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error |
||
| 109 | trigger_error( |
||
| 110 | esc_html(sprintf( |
||
| 111 | 'PUC %s does not support updates for %ss %s', |
||
| 112 | self::$latestCompatibleVersion, |
||
| 113 | strtolower($type), |
||
| 114 | $service ? ('hosted on ' . $service) : 'using JSON metadata' |
||
| 115 | )), |
||
| 116 | E_USER_ERROR |
||
| 117 | ); |
||
| 118 | } |
||
| 119 | |||
| 120 | if ( !isset($apiClass) ) { |
||
| 121 | //Plain old update checker. |
||
| 122 | return new $checkerClass($metadataUrl, $id, $slug, $checkPeriod, $optionName, $muPluginFile); |
||
| 123 | } else { |
||
| 124 | //VCS checker + an API client. |
||
| 125 | $apiClass = self::getCompatibleClassVersion($apiClass); |
||
| 126 | if ( $apiClass === null ) { |
||
| 127 | //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error |
||
| 128 | trigger_error(esc_html(sprintf( |
||
| 129 | 'PUC %s does not support %s', |
||
| 130 | self::$latestCompatibleVersion, |
||
| 131 | $service |
||
| 132 | )), E_USER_ERROR); |
||
| 133 | } |
||
| 134 | |||
| 135 | return new $checkerClass( |
||
| 136 | new $apiClass($metadataUrl), |
||
| 137 | $id, |
||
| 138 | $slug, |
||
| 139 | $checkPeriod, |
||
| 140 | $optionName, |
||
| 141 | $muPluginFile |
||
| 142 | ); |
||
| 143 | } |
||
| 144 | } |
||
| 145 | |||
| 146 | /** |
||
| 147 | * |
||
| 148 | * Normalize a filesystem path. Introduced in WP 3.9. |
||
| 149 | * Copying here allows use of the class on earlier versions. |
||
| 150 | * This version adapted from WP 4.8.2 (unchanged since 4.5.0) |
||
| 151 | * |
||
| 152 | * @param string $path Path to normalize. |
||
| 153 | * @return string Normalized path. |
||
| 154 | */ |
||
| 155 | public static function normalizePath($path) { |
||
| 156 | if ( function_exists('wp_normalize_path') ) { |
||
| 157 | return wp_normalize_path($path); |
||
| 158 | } |
||
| 159 | $path = str_replace('\\', '/', $path); |
||
| 160 | $path = preg_replace('|(?<=.)/+|', '/', $path); |
||
| 161 | if ( substr($path, 1, 1) === ':' ) { |
||
| 162 | $path = ucfirst($path); |
||
| 163 | } |
||
| 164 | return $path; |
||
| 165 | } |
||
| 166 | |||
| 167 | /** |
||
| 168 | * Check if the path points to a plugin file. |
||
| 169 | * |
||
| 170 | * @param string $absolutePath Normalized path. |
||
| 171 | * @return bool |
||
| 172 | */ |
||
| 173 | protected static function isPluginFile($absolutePath) { |
||
| 174 | //Is the file inside the "plugins" or "mu-plugins" directory? |
||
| 175 | $pluginDir = self::normalizePath(WP_PLUGIN_DIR); |
||
| 176 | $muPluginDir = self::normalizePath(WPMU_PLUGIN_DIR); |
||
| 177 | if ( (strpos($absolutePath, $pluginDir) === 0) || (strpos($absolutePath, $muPluginDir) === 0) ) { |
||
| 178 | return true; |
||
| 179 | } |
||
| 180 | |||
| 181 | //Is it a file at all? Caution: is_file() can fail if the parent dir. doesn't have the +x permission set. |
||
| 182 | if ( !is_file($absolutePath) ) { |
||
| 183 | return false; |
||
| 184 | } |
||
| 185 | |||
| 186 | //Does it have a valid plugin header? |
||
| 187 | //This is a last-ditch check for plugins symlinked from outside the WP root. |
||
| 188 | if ( function_exists('get_file_data') ) { |
||
| 189 | $headers = get_file_data($absolutePath, array('Name' => 'Plugin Name'), 'plugin'); |
||
| 190 | return !empty($headers['Name']); |
||
| 191 | } |
||
| 192 | |||
| 193 | return false; |
||
| 194 | } |
||
| 195 | |||
| 196 | /** |
||
| 197 | * Get the name of the theme's directory from a full path to a file inside that directory. |
||
| 198 | * E.g. "/abc/public_html/wp-content/themes/foo/whatever.php" => "foo". |
||
| 199 | * |
||
| 200 | * Note that subdirectories are currently not supported. For example, |
||
| 201 | * "/xyz/wp-content/themes/my-theme/includes/whatever.php" => NULL. |
||
| 202 | * |
||
| 203 | * @param string $absolutePath Normalized path. |
||
| 204 | * @return string|null Directory name, or NULL if the path doesn't point to a theme. |
||
| 205 | */ |
||
| 206 | protected static function getThemeDirectoryName($absolutePath) { |
||
| 207 | if ( is_file($absolutePath) ) { |
||
| 208 | $absolutePath = dirname($absolutePath); |
||
| 209 | } |
||
| 210 | |||
| 211 | if ( file_exists($absolutePath . '/style.css') ) { |
||
| 212 | return basename($absolutePath); |
||
| 213 | } |
||
| 214 | return null; |
||
| 215 | } |
||
| 216 | |||
| 217 | /** |
||
| 218 | * Get the service URI from the file header. |
||
| 219 | * |
||
| 220 | * @param string $fullPath |
||
| 221 | * @return string |
||
| 222 | */ |
||
| 223 | private static function getServiceURI($fullPath) { |
||
| 243 | ); |
||
| 244 | } |
||
| 245 | |||
| 246 | /** |
||
| 247 | * Get the name of the hosting service that the URL points to. |
||
| 248 | * |
||
| 249 | * @param string $metadataUrl |
||
| 250 | * @return string|null |
||
| 251 | */ |
||
| 252 | private static function getVcsService($metadataUrl) { |
||
| 277 | } |
||
| 278 | |||
| 279 | /** |
||
| 280 | * Get the latest version of the specified class that has the same major version number |
||
| 281 | * as this factory class. |
||
| 282 | * |
||
| 283 | * @param string $class Partial class name. |
||
| 284 | * @return string|null Full class name. |
||
| 285 | */ |
||
| 286 | protected static function getCompatibleClassVersion($class) { |
||
| 287 | if ( isset(self::$classVersions[$class][self::$latestCompatibleVersion]) ) { |
||
| 288 | return self::$classVersions[$class][self::$latestCompatibleVersion]; |
||
| 289 | } |
||
| 290 | return null; |
||
| 291 | } |
||
| 292 | |||
| 293 | /** |
||
| 294 | * Get the specific class name for the latest available version of a class. |
||
| 295 | * |
||
| 296 | * @param string $class |
||
| 297 | * @return null|string |
||
| 298 | */ |
||
| 299 | public static function getLatestClassVersion($class) { |
||
| 308 | } |
||
| 309 | } |
||
| 310 | |||
| 311 | /** |
||
| 312 | * Sort available class versions in descending order (i.e. newest first). |
||
| 313 | */ |
||
| 314 | protected static function sortVersions() { |
||
| 315 | foreach ( self::$classVersions as $class => $versions ) { |
||
| 316 | uksort($versions, array(__CLASS__, 'compareVersions')); |
||
| 317 | self::$classVersions[$class] = $versions; |
||
| 318 | } |
||
| 319 | self::$sorted = true; |
||
| 320 | } |
||
| 321 | |||
| 322 | protected static function compareVersions($a, $b) { |
||
| 324 | } |
||
| 325 | |||
| 326 | /** |
||
| 327 | * Register a version of a class. |
||
| 328 | * |
||
| 329 | * @access private This method is only for internal use by the library. |
||
| 330 | * |
||
| 331 | * @param string $generalClass Class name without version numbers, e.g. 'PluginUpdateChecker'. |
||
| 332 | * @param string $versionedClass Actual class name, e.g. 'PluginUpdateChecker_1_2'. |
||
| 333 | * @param string $version Version number, e.g. '1.2'. |
||
| 334 | */ |
||
| 335 | public static function addVersion($generalClass, $versionedClass, $version) { |
||
| 359 | } |
||
| 360 | } |
||
| 361 | |||
| 363 |