├── .gitignore ├── composer.json ├── README.md ├── includes ├── Plugin.php ├── ThemeUpdater.php └── PluginUpdater.php └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-forge/wp-update-handler", 3 | "description": "A WordPress package for updating custom plugins and themes based on an API response from a custom update server.", 4 | "license": "GPL-2.0-or-later", 5 | "authors": [ 6 | { 7 | "name": "Micah Wood", 8 | "email": "micah@wpscholar.com" 9 | } 10 | ], 11 | "autoload": { 12 | "psr-4": { 13 | "WP_Forge\\WPUpdateHandler\\": "includes/" 14 | } 15 | }, 16 | "require": { 17 | "wp-forge/helpers": "^2.0" 18 | }, 19 | "require-dev": { 20 | "wpscholar/phpcs-standards-wpscholar": "^1.0" 21 | }, 22 | "scripts": { 23 | "lint": [ 24 | "vendor/bin/phpcs -s --standard=WPScholar ." 25 | ], 26 | "fix": [ 27 | "vendor/bin/phpcbf -s --standard=WPScholar ." 28 | ] 29 | }, 30 | "config": { 31 | "allow-plugins": { 32 | "dealerdirect/phpcodesniffer-composer-installer": true 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordPress Update Handler 2 | 3 | A WordPress package for updating custom plugins and themes based on an JSON REST API response from a custom update 4 | server. 5 | 6 | Check out the [WordPress GitHub Release API](https://github.com/wp-forge/worker-wp-github-release-api) repository to 7 | learn how to quickly launch a custom update server that fetches releases from GitHub using Cloudflare Workers. 8 | 9 | ## Plugins 10 | 11 | This package expects your custom plugin info API to respond with the same shape as 12 | the [WordPress plugin info API](https://codex.wordpress.org/WordPress.org_API#Plugins). However, if your API response 13 | has a different shape, you can map fields to those returned by your API. 14 | 15 | ### Usage 16 | 17 | Basic example: 18 | 19 | ```php 20 | setDataMap( 59 | [ 60 | 'requires' => 'requires.wp', 61 | 'requires' => 'requires.php', 62 | 'banners.2x' => 'banners.retina', 63 | ] 64 | ); 65 | 66 | /* 67 | * Explicitly set specific values that will be provided to WordPress. 68 | */ 69 | $pluginUpdater->setDataOverrides( 70 | [ 71 | 'banners' => [ 72 | '2x' => 'https://my.cdn.com/banner-123-retina.jpg', 73 | '1x' => 'https://my.cdn.com/banner-123.jpg', 74 | ], 75 | 'icons' => [ 76 | '2x' => 'https://my.cdn.com/icon-123-retina.jpg', 77 | '1x' => 'https://my.cdn.com/icon-123.jpg', 78 | ], 79 | ] 80 | ); 81 | 82 | ``` 83 | 84 | ## Themes 85 | 86 | This package expects your custom theme info API to respond with the same shape as 87 | the [WordPress theme info API](https://codex.wordpress.org/WordPress.org_API#Themes). However, if your API response has 88 | a different shape, you can map fields to those returned by your API. 89 | 90 | ### Usage 91 | 92 | Basic example: 93 | 94 | ```php 95 | setDataMap( 134 | [ 135 | 'requires' => 'requires.wp', 136 | 'requires' => 'requires.php', 137 | 'banners.2x' => 'banners.retina', 138 | ] 139 | ); 140 | 141 | /* 142 | * Explicitly set specific values that will be provided to WordPress. 143 | */ 144 | $themeUpdater->setDataOverrides( 145 | [ 146 | 'banners' => [ 147 | '2x' => 'https://my.cdn.com/banner-123-retina.jpg', 148 | '1x' => 'https://my.cdn.com/banner-123.jpg', 149 | ], 150 | 'icons' => [ 151 | '2x' => 'https://my.cdn.com/icon-123-retina.jpg', 152 | '1x' => 'https://my.cdn.com/icon-123.jpg', 153 | ], 154 | ] 155 | ); 156 | 157 | ``` 158 | -------------------------------------------------------------------------------- /includes/Plugin.php: -------------------------------------------------------------------------------- 1 | 'Author', 35 | 'author_name' => 'AuthorName', 36 | 'author_uri' => 'AuthorURI', 37 | 'description' => 'Description', 38 | 'domain_path' => 'DomainPath', 39 | 'license' => 'License', 40 | 'license_uri' => 'LicenseURI', 41 | 'name' => 'Name', 42 | 'network' => 'Network', 43 | 'requires_wp' => 'RequiresWP', 44 | 'requires_php' => 'RequiresPHP', 45 | 'text_domain' => 'TextDomain', 46 | 'title' => 'Title', 47 | 'uri' => 'PluginURI', 48 | 'version' => 'Version', 49 | ); 50 | 51 | /** 52 | * The absolute path to the plugin file. 53 | * 54 | * @var string 55 | */ 56 | protected $file; 57 | 58 | /** 59 | * Plugin file headers. 60 | * 61 | * @var array 62 | */ 63 | protected $file_headers; 64 | 65 | /** 66 | * Constructor. 67 | * 68 | * @param string $file The plugin basename, or absolute path to the plugin file. 69 | */ 70 | public function __construct( $file ) { 71 | 72 | // If plugin basename is provided, convert to full file path 73 | if ( 0 !== strpos( $file, '/' ) ) { 74 | $file = WP_PLUGIN_DIR . '/' . $file; 75 | } 76 | 77 | $this->file = $file; 78 | } 79 | 80 | /** 81 | * Get the plugin basename. 82 | * 83 | * @return string 84 | */ 85 | public function basename() { 86 | return plugin_basename( $this->file ); 87 | } 88 | 89 | /** 90 | * Get the absolute path to the plugin file. 91 | * 92 | * @return string 93 | */ 94 | public function file() { 95 | return $this->file; 96 | } 97 | 98 | /** 99 | * Get the plugin slug. 100 | * 101 | * @return string 102 | */ 103 | public function slug() { 104 | return basename( plugin_dir_path( $this->file ) ); 105 | } 106 | 107 | /** 108 | * Get a specific plugin file header. 109 | * 110 | * @param string $name The plugin file header name. 111 | * 112 | * @return string 113 | */ 114 | protected function get_file_header( $name ) { 115 | $file_headers = $this->get_file_headers(); 116 | 117 | return (string) isset( $file_headers[ $name ] ) ? $file_headers[ $name ] : ''; 118 | } 119 | 120 | /** 121 | * Get all plugin file headers. 122 | * 123 | * @return array 124 | */ 125 | protected function get_file_headers() { 126 | 127 | if ( isset( $this->file_headers ) ) { 128 | return $this->file_headers; 129 | } 130 | 131 | if ( ! function_exists( 'get_plugin_data' ) ) { 132 | require wp_normalize_path( ABSPATH . '/wp-admin/includes/plugin.php' ); 133 | } 134 | 135 | $this->file_headers = get_plugin_data( $this->file ); 136 | 137 | return $this->file_headers; 138 | } 139 | 140 | /** 141 | * Magic method for fetching data from plugin file headers. 142 | * 143 | * @param string $name The method name. 144 | * @param array $args The method parameters. 145 | * 146 | * @return string 147 | */ 148 | public function __call( $name, $args ) { 149 | $value = ''; 150 | if ( $this->offsetExists( $name ) ) { 151 | $value = $this->offsetGet( $name ); 152 | } 153 | 154 | return $value; 155 | } 156 | 157 | /** 158 | * Check if array offset exists. 159 | * 160 | * @param string $offset Array key 161 | * 162 | * @return bool 163 | */ 164 | #[\ReturnTypeWillChange] 165 | public function offsetExists( $offset ) { 166 | return array_key_exists( $offset, self::HEADERS ) || in_array( $offset, array( 'basename', 'file', 'slug' ), true ); 167 | } 168 | 169 | /** 170 | * Get array offset. 171 | * 172 | * @param string $offset Array key 173 | * 174 | * @return string 175 | */ 176 | #[\ReturnTypeWillChange] 177 | public function offsetGet( $offset ) { 178 | if ( method_exists( $this, $offset ) ) { 179 | return $this->{$offset}(); 180 | } 181 | if ( array_key_exists( $offset, self::HEADERS ) ) { 182 | return $this->get_file_header( self::HEADERS[ $offset ] ); 183 | } 184 | 185 | return null; 186 | } 187 | 188 | /** 189 | * Set array value. 190 | * 191 | * @param string $offset Array key 192 | * @param mixed $value Value to set 193 | * 194 | * @throws \Exception If called. 195 | */ 196 | #[\ReturnTypeWillChange] 197 | public function offsetSet( $offset, $value ) { 198 | throw new \Exception( 'Setting plugin values is not allowed!' ); 199 | } 200 | 201 | /** 202 | * Unset array value. 203 | * 204 | * @param string $offset Array key 205 | * 206 | * @throws \Exception If called. 207 | */ 208 | #[\ReturnTypeWillChange] 209 | public function offsetUnset( $offset ) { 210 | throw new \Exception( 'Unsetting plugin values is not allowed!' ); 211 | } 212 | 213 | /** 214 | * Convert class instance into an array. 215 | * 216 | * @return array 217 | */ 218 | public function toArray() { 219 | $keys = array_merge( array_keys( self::HEADERS ), array( 'basename', 'slug' ) ); 220 | asort( $keys ); 221 | 222 | $values = array(); 223 | foreach ( $keys as $key ) { 224 | $values[ $key ] = $this->offsetGet( $key ); 225 | } 226 | 227 | return $values; 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /includes/ThemeUpdater.php: -------------------------------------------------------------------------------- 1 | setTheme( $theme )->setUrl( $url )->registerHooks(); 63 | } 64 | 65 | /** 66 | * Set up theme instance. 67 | * 68 | * @param \WP_Theme $theme The theme instance. 69 | * 70 | * @return $this 71 | */ 72 | public function setTheme( \WP_Theme $theme ) { 73 | $this->theme = $theme; 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * Set the theme update API URL. 80 | * 81 | * @param string $url The theme update API URL. 82 | * 83 | * @return $this 84 | */ 85 | public function setUrl( $url ) { 86 | $this->url = $url; 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * Set the cache expiration. 93 | * 94 | * @param int $expiration Duration in seconds until cache expires. 95 | * 96 | * @return $this 97 | */ 98 | public function setCacheExpiration( $expiration ) { 99 | $this->cacheExpiration = absint( $expiration ); 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * Set data map. 106 | * 107 | * @param array $map A mapping of API response fields to the expected WP fields. 108 | * 109 | * @return $this 110 | */ 111 | public function setDataMap( array $map ) { 112 | $this->dataMap = $map; 113 | 114 | return $this; 115 | } 116 | 117 | /** 118 | * Set data overrides. 119 | * 120 | * @param array $overrides A key-value store of fields to replace with specific values. 121 | * 122 | * @return $this 123 | */ 124 | public function setDataOverrides( array $overrides ) { 125 | $this->dataOverrides = $overrides; 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * Check if an update is available. 132 | * 133 | * @return bool 134 | */ 135 | public function hasUpdate() { 136 | $release = $this->getRelease(); 137 | 138 | return isset( $release['version'] ) && version_compare( $release['version'], $this->theme->get( 'Version' ), '>' ); 139 | } 140 | 141 | /** 142 | * Fetch details on the latest theme release. 143 | * 144 | * @return array The latest release data. 145 | */ 146 | public function getRelease() { 147 | $cache_key = 'wp_theme_update_' . $this->theme->get_stylesheet(); 148 | delete_transient( $cache_key ); 149 | $payload = get_transient( $cache_key ); 150 | if ( ! $payload ) { 151 | $payload = array(); 152 | $response = wp_remote_get( $this->url ); 153 | 154 | if ( 200 === wp_remote_retrieve_response_code( $response ) ) { 155 | $body = wp_remote_retrieve_body( $response ); 156 | if ( $body ) { 157 | $data = json_decode( $body, true ); 158 | if ( ! is_null( $data ) ) { 159 | $payload = $this->mapData( $data ); 160 | set_transient( $cache_key, $payload, $this->cacheExpiration ); 161 | } 162 | } 163 | } 164 | } 165 | 166 | return $payload; 167 | } 168 | 169 | /** 170 | * Normalize data from the API to the expected values. 171 | * 172 | * @param array $data A mapping of the local key to the remote key using dot notation. 173 | * 174 | * @return array 175 | */ 176 | public function mapData( array $data ) { 177 | 178 | $author_name = $this->theme->get( 'Author' ); 179 | $author_uri = $this->theme->get( 'AuthorURI' ); 180 | 181 | $author = ! empty( $author_uri ) ? "{$author_name}" : $author_name; 182 | 183 | $description = dataGet( $data, 'description', $this->theme->get( 'Description' ) ); 184 | 185 | $defaults = array( 186 | 'author' => $author, 187 | 'author_name' => $author_name, 188 | 'author_uri' => $author_uri, 189 | 'description' => $description, 190 | 'download_link' => dataGet( $data, 'download_link' ), 191 | 'homepage' => dataGet( $data, 'homepage', $this->theme->get( 'ThemeURI' ) ), 192 | 'name' => $this->theme->get( 'Name' ), 193 | 'theme' => $this->theme->get_stylesheet(), 194 | 'requires' => dataGet( $data, 'requires', $this->theme->get( 'RequiresWP' ) ), 195 | 'requires_php' => dataGet( $data, 'requires_php', $this->theme->get( 'RequiresPHP' ) ), 196 | 'slug' => $this->theme->get_stylesheet(), 197 | 'tested' => dataGet( $data, 'tested' ), 198 | 'version' => dataGet( $data, 'version' ), 199 | ); 200 | 201 | $payload = $defaults; 202 | 203 | // Map selected fields 204 | foreach ( $this->dataMap as $key => $target ) { 205 | dataSet( $payload, $key, dataGet( $data, $target ) ); 206 | } 207 | 208 | // Override selected fields 209 | foreach ( $this->dataOverrides as $key => $value ) { 210 | dataSet( $payload, $key, $value ); 211 | } 212 | 213 | $payload['new_version'] = $payload['version']; 214 | $payload['package'] = $payload['download_link']; 215 | $payload['url'] = $payload['homepage']; 216 | 217 | return $payload; 218 | } 219 | 220 | /** 221 | * Register hooks. 222 | */ 223 | protected function registerHooks() { 224 | 225 | add_filter( 226 | 'site_transient_update_themes', 227 | function ( $transient ) { 228 | 229 | if ( empty( $transient ) || ! is_object( $transient ) ) { 230 | return $transient; 231 | } 232 | 233 | $release = $this->getRelease(); 234 | 235 | if ( $this->hasUpdate() ) { 236 | $transient->response[ $this->theme->get_stylesheet() ] = $release; 237 | } else { 238 | $transient->no_update[ $this->theme->get_stylesheet() ] = $release; 239 | } 240 | 241 | return $transient; 242 | } 243 | ); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /includes/PluginUpdater.php: -------------------------------------------------------------------------------- 1 | setPlugin( $file )->setUrl( $url )->registerHooks(); 64 | } 65 | 66 | /** 67 | * Set up plugin instance. 68 | * 69 | * @param string $file The plugin basename or absolute path to main plugin file. 70 | * 71 | * @return $this 72 | */ 73 | public function setPlugin( $file ) { 74 | $this->plugin = new Plugin( $file ); 75 | 76 | return $this; 77 | } 78 | 79 | /** 80 | * Set the plugin update API URL. 81 | * 82 | * @param string $url The plugin update API URL. 83 | * 84 | * @return $this 85 | */ 86 | public function setUrl( $url ) { 87 | $this->url = $url; 88 | 89 | return $this; 90 | } 91 | 92 | /** 93 | * Set the cache expiration. 94 | * 95 | * @param int $expiration Duration in seconds until cache expires. 96 | * 97 | * @return $this 98 | */ 99 | public function setCacheExpiration( $expiration ) { 100 | $this->cacheExpiration = absint( $expiration ); 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * Set data map. 107 | * 108 | * @param array $map A mapping of API response fields to the expected WP fields. 109 | * 110 | * @return $this 111 | */ 112 | public function setDataMap( array $map ) { 113 | $this->dataMap = $map; 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * Set data overrides. 120 | * 121 | * @param array $overrides A key-value store of fields to replace with specific values. 122 | * 123 | * @return $this 124 | */ 125 | public function setDataOverrides( array $overrides ) { 126 | $this->dataOverrides = $overrides; 127 | 128 | return $this; 129 | } 130 | 131 | /** 132 | * Check if an update is available. 133 | * 134 | * @return bool 135 | */ 136 | public function hasUpdate() { 137 | $release = $this->getRelease(); 138 | 139 | return isset( $release->version ) && version_compare( $release->version, $this->plugin->version(), '>' ); 140 | } 141 | 142 | /** 143 | * Fetch details on the latest plugin release. 144 | * 145 | * @return \stdClass The latest release data. 146 | */ 147 | public function getRelease() { 148 | $cache_key = 'wp_plugin_update_' . $this->plugin->slug(); 149 | $payload = get_transient( $cache_key ); 150 | if ( ! $payload ) { 151 | $payload = new \stdClass(); 152 | $response = wp_remote_get( $this->url ); 153 | 154 | if ( 200 === wp_remote_retrieve_response_code( $response ) ) { 155 | $body = wp_remote_retrieve_body( $response ); 156 | if ( $body ) { 157 | $data = json_decode( $body, true ); 158 | if ( ! is_null( $data ) ) { 159 | $payload = $this->mapData( $data ); 160 | set_transient( $cache_key, $payload, $this->cacheExpiration ); 161 | } 162 | } 163 | } 164 | } 165 | 166 | return $payload; 167 | } 168 | 169 | /** 170 | * Normalize data from the API to the expected values. 171 | * 172 | * @param array $data A mapping of the local key to the remote key using dot notation. 173 | * 174 | * @return \stdClass 175 | */ 176 | public function mapData( array $data ) { 177 | 178 | $author_name = $this->plugin->author_name(); 179 | $author_uri = $this->plugin->author_uri(); 180 | 181 | $author = ! empty( $author_uri ) ? "{$author_name}" : $author_name; 182 | 183 | $description = dataGet( $data, 'description', $this->plugin->description() ); 184 | 185 | $defaults = array( 186 | 'author' => $author, 187 | 'author_name' => $author_name, 188 | 'author_uri' => $author_uri, 189 | 'description' => $description, 190 | 'download_link' => dataGet( $data, 'download_link' ), 191 | 'homepage' => dataGet( $data, 'homepage', $this->plugin->uri() ), 192 | 'id' => $this->plugin->basename(), 193 | 'last_updated' => dataGet( $data, 'last_updated' ), 194 | 'name' => $this->plugin->name(), 195 | 'plugin' => $this->plugin->basename(), 196 | 'requires' => dataGet( $data, 'requires', $this->plugin->requires_wp() ), 197 | 'requires_php' => dataGet( $data, 'requires_php', $this->plugin->requires_php() ), 198 | 'sections' => array( 199 | 'description' => $description, 200 | ), 201 | 'short_description' => $description, 202 | 'slug' => $this->plugin->slug(), 203 | 'tested' => dataGet( $data, 'tested' ), 204 | 'version' => dataGet( $data, 'version' ), 205 | ); 206 | 207 | $payload = $defaults; 208 | 209 | // Map selected fields 210 | foreach ( $this->dataMap as $key => $target ) { 211 | dataSet( $payload, $key, dataGet( $data, $target ) ); 212 | } 213 | 214 | // Override selected fields 215 | foreach ( $this->dataOverrides as $key => $value ) { 216 | dataSet( $payload, $key, $value ); 217 | } 218 | 219 | $payload['new_version'] = $payload['version']; 220 | $payload['package'] = $payload['download_link']; 221 | $payload['url'] = $payload['homepage']; 222 | 223 | if ( isset( $payload['banners']['2x'] ) && ! isset( $payload['banners']['high'] ) ) { 224 | $payload['banners']['high'] = $payload['banners']['2x']; 225 | } 226 | 227 | if ( isset( $payload['banners']['1x'] ) && ! isset( $payload['banners']['low'] ) ) { 228 | $payload['banners']['low'] = $payload['banners']['1x']; 229 | } 230 | 231 | return (object) $payload; 232 | } 233 | 234 | /** 235 | * Register hooks. 236 | */ 237 | protected function registerHooks() { 238 | 239 | add_action( 240 | 'plugins_api', 241 | function ( $response, $action, $args ) { 242 | 243 | if ( isset( $args->slug ) && $args->slug === $this->plugin->slug() ) { 244 | return $this->getRelease(); 245 | } 246 | 247 | return $response; 248 | }, 249 | 20, 250 | 3 251 | ); 252 | 253 | add_filter( 254 | 'site_transient_update_plugins', 255 | function ( $transient ) { 256 | 257 | if ( empty( $transient ) || ! is_object( $transient ) ) { 258 | return $transient; 259 | } 260 | 261 | $release = $this->getRelease(); 262 | 263 | if ( $this->hasUpdate() ) { 264 | $transient->response[ $this->plugin->basename() ] = $release; 265 | } else { 266 | $transient->no_update[ $this->plugin->basename() ] = $release; 267 | } 268 | 269 | return $transient; 270 | } 271 | ); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "2be39584906963f91534d6f7ab38e7a0", 8 | "packages": [ 9 | { 10 | "name": "doctrine/inflector", 11 | "version": "1.4.4", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/doctrine/inflector.git", 15 | "reference": "4bd5c1cdfcd00e9e2d8c484f79150f67e5d355d9" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/doctrine/inflector/zipball/4bd5c1cdfcd00e9e2d8c484f79150f67e5d355d9", 20 | "reference": "4bd5c1cdfcd00e9e2d8c484f79150f67e5d355d9", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": "^7.1 || ^8.0" 25 | }, 26 | "require-dev": { 27 | "doctrine/coding-standard": "^8.0", 28 | "phpstan/phpstan": "^0.12", 29 | "phpstan/phpstan-phpunit": "^0.12", 30 | "phpstan/phpstan-strict-rules": "^0.12", 31 | "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" 32 | }, 33 | "type": "library", 34 | "extra": { 35 | "branch-alias": { 36 | "dev-master": "2.0.x-dev" 37 | } 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "Doctrine\\Inflector\\": "lib/Doctrine/Inflector", 42 | "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" 43 | } 44 | }, 45 | "notification-url": "https://packagist.org/downloads/", 46 | "license": [ 47 | "MIT" 48 | ], 49 | "authors": [ 50 | { 51 | "name": "Guilherme Blanco", 52 | "email": "guilhermeblanco@gmail.com" 53 | }, 54 | { 55 | "name": "Roman Borschel", 56 | "email": "roman@code-factory.org" 57 | }, 58 | { 59 | "name": "Benjamin Eberlei", 60 | "email": "kontakt@beberlei.de" 61 | }, 62 | { 63 | "name": "Jonathan Wage", 64 | "email": "jonwage@gmail.com" 65 | }, 66 | { 67 | "name": "Johannes Schmitt", 68 | "email": "schmittjoh@gmail.com" 69 | } 70 | ], 71 | "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", 72 | "homepage": "https://www.doctrine-project.org/projects/inflector.html", 73 | "keywords": [ 74 | "inflection", 75 | "inflector", 76 | "lowercase", 77 | "manipulation", 78 | "php", 79 | "plural", 80 | "singular", 81 | "strings", 82 | "uppercase", 83 | "words" 84 | ], 85 | "support": { 86 | "issues": "https://github.com/doctrine/inflector/issues", 87 | "source": "https://github.com/doctrine/inflector/tree/1.4.4" 88 | }, 89 | "funding": [ 90 | { 91 | "url": "https://www.doctrine-project.org/sponsorship.html", 92 | "type": "custom" 93 | }, 94 | { 95 | "url": "https://www.patreon.com/phpdoctrine", 96 | "type": "patreon" 97 | }, 98 | { 99 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", 100 | "type": "tidelift" 101 | } 102 | ], 103 | "time": "2021-04-16T17:34:40+00:00" 104 | }, 105 | { 106 | "name": "wp-forge/helpers", 107 | "version": "2.0", 108 | "source": { 109 | "type": "git", 110 | "url": "https://github.com/wp-forge/helpers.git", 111 | "reference": "28ebc09a3390dbff427270a4ed2b93395e8b9b78" 112 | }, 113 | "dist": { 114 | "type": "zip", 115 | "url": "https://api.github.com/repos/wp-forge/helpers/zipball/28ebc09a3390dbff427270a4ed2b93395e8b9b78", 116 | "reference": "28ebc09a3390dbff427270a4ed2b93395e8b9b78", 117 | "shasum": "" 118 | }, 119 | "require": { 120 | "doctrine/inflector": "^1.3", 121 | "ext-mbstring": "*" 122 | }, 123 | "type": "library", 124 | "autoload": { 125 | "files": [ 126 | "includes/functions.php" 127 | ], 128 | "psr-4": { 129 | "WP_Forge\\Helpers\\": "includes" 130 | } 131 | }, 132 | "notification-url": "https://packagist.org/downloads/", 133 | "license": [ 134 | "GPL-2.0-or-later" 135 | ], 136 | "authors": [ 137 | { 138 | "name": "Micah Wood", 139 | "email": "micah@wpscholar.com" 140 | } 141 | ], 142 | "description": "A collection of helpers for WordPress and PHP development.", 143 | "support": { 144 | "issues": "https://github.com/wp-forge/helpers/issues", 145 | "source": "https://github.com/wp-forge/helpers/tree/2.0" 146 | }, 147 | "time": "2022-01-06T13:10:47+00:00" 148 | } 149 | ], 150 | "packages-dev": [ 151 | { 152 | "name": "dealerdirect/phpcodesniffer-composer-installer", 153 | "version": "v1.0.0", 154 | "source": { 155 | "type": "git", 156 | "url": "https://github.com/PHPCSStandards/composer-installer.git", 157 | "reference": "4be43904336affa5c2f70744a348312336afd0da" 158 | }, 159 | "dist": { 160 | "type": "zip", 161 | "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", 162 | "reference": "4be43904336affa5c2f70744a348312336afd0da", 163 | "shasum": "" 164 | }, 165 | "require": { 166 | "composer-plugin-api": "^1.0 || ^2.0", 167 | "php": ">=5.4", 168 | "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" 169 | }, 170 | "require-dev": { 171 | "composer/composer": "*", 172 | "ext-json": "*", 173 | "ext-zip": "*", 174 | "php-parallel-lint/php-parallel-lint": "^1.3.1", 175 | "phpcompatibility/php-compatibility": "^9.0", 176 | "yoast/phpunit-polyfills": "^1.0" 177 | }, 178 | "type": "composer-plugin", 179 | "extra": { 180 | "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" 181 | }, 182 | "autoload": { 183 | "psr-4": { 184 | "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" 185 | } 186 | }, 187 | "notification-url": "https://packagist.org/downloads/", 188 | "license": [ 189 | "MIT" 190 | ], 191 | "authors": [ 192 | { 193 | "name": "Franck Nijhof", 194 | "email": "franck.nijhof@dealerdirect.com", 195 | "homepage": "http://www.frenck.nl", 196 | "role": "Developer / IT Manager" 197 | }, 198 | { 199 | "name": "Contributors", 200 | "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" 201 | } 202 | ], 203 | "description": "PHP_CodeSniffer Standards Composer Installer Plugin", 204 | "homepage": "http://www.dealerdirect.com", 205 | "keywords": [ 206 | "PHPCodeSniffer", 207 | "PHP_CodeSniffer", 208 | "code quality", 209 | "codesniffer", 210 | "composer", 211 | "installer", 212 | "phpcbf", 213 | "phpcs", 214 | "plugin", 215 | "qa", 216 | "quality", 217 | "standard", 218 | "standards", 219 | "style guide", 220 | "stylecheck", 221 | "tests" 222 | ], 223 | "support": { 224 | "issues": "https://github.com/PHPCSStandards/composer-installer/issues", 225 | "source": "https://github.com/PHPCSStandards/composer-installer" 226 | }, 227 | "time": "2023-01-05T11:28:13+00:00" 228 | }, 229 | { 230 | "name": "phpcompatibility/php-compatibility", 231 | "version": "9.3.5", 232 | "source": { 233 | "type": "git", 234 | "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", 235 | "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" 236 | }, 237 | "dist": { 238 | "type": "zip", 239 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", 240 | "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", 241 | "shasum": "" 242 | }, 243 | "require": { 244 | "php": ">=5.3", 245 | "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" 246 | }, 247 | "conflict": { 248 | "squizlabs/php_codesniffer": "2.6.2" 249 | }, 250 | "require-dev": { 251 | "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" 252 | }, 253 | "suggest": { 254 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", 255 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." 256 | }, 257 | "type": "phpcodesniffer-standard", 258 | "notification-url": "https://packagist.org/downloads/", 259 | "license": [ 260 | "LGPL-3.0-or-later" 261 | ], 262 | "authors": [ 263 | { 264 | "name": "Wim Godden", 265 | "homepage": "https://github.com/wimg", 266 | "role": "lead" 267 | }, 268 | { 269 | "name": "Juliette Reinders Folmer", 270 | "homepage": "https://github.com/jrfnl", 271 | "role": "lead" 272 | }, 273 | { 274 | "name": "Contributors", 275 | "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" 276 | } 277 | ], 278 | "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", 279 | "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", 280 | "keywords": [ 281 | "compatibility", 282 | "phpcs", 283 | "standards" 284 | ], 285 | "support": { 286 | "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", 287 | "source": "https://github.com/PHPCompatibility/PHPCompatibility" 288 | }, 289 | "time": "2019-12-27T09:44:58+00:00" 290 | }, 291 | { 292 | "name": "phpcompatibility/phpcompatibility-paragonie", 293 | "version": "1.3.2", 294 | "source": { 295 | "type": "git", 296 | "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", 297 | "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26" 298 | }, 299 | "dist": { 300 | "type": "zip", 301 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", 302 | "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", 303 | "shasum": "" 304 | }, 305 | "require": { 306 | "phpcompatibility/php-compatibility": "^9.0" 307 | }, 308 | "require-dev": { 309 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7", 310 | "paragonie/random_compat": "dev-master", 311 | "paragonie/sodium_compat": "dev-master" 312 | }, 313 | "suggest": { 314 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", 315 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." 316 | }, 317 | "type": "phpcodesniffer-standard", 318 | "notification-url": "https://packagist.org/downloads/", 319 | "license": [ 320 | "LGPL-3.0-or-later" 321 | ], 322 | "authors": [ 323 | { 324 | "name": "Wim Godden", 325 | "role": "lead" 326 | }, 327 | { 328 | "name": "Juliette Reinders Folmer", 329 | "role": "lead" 330 | } 331 | ], 332 | "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", 333 | "homepage": "http://phpcompatibility.com/", 334 | "keywords": [ 335 | "compatibility", 336 | "paragonie", 337 | "phpcs", 338 | "polyfill", 339 | "standards", 340 | "static analysis" 341 | ], 342 | "support": { 343 | "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", 344 | "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" 345 | }, 346 | "time": "2022-10-25T01:46:02+00:00" 347 | }, 348 | { 349 | "name": "phpcompatibility/phpcompatibility-wp", 350 | "version": "2.1.4", 351 | "source": { 352 | "type": "git", 353 | "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", 354 | "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5" 355 | }, 356 | "dist": { 357 | "type": "zip", 358 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", 359 | "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", 360 | "shasum": "" 361 | }, 362 | "require": { 363 | "phpcompatibility/php-compatibility": "^9.0", 364 | "phpcompatibility/phpcompatibility-paragonie": "^1.0" 365 | }, 366 | "require-dev": { 367 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7" 368 | }, 369 | "suggest": { 370 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", 371 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." 372 | }, 373 | "type": "phpcodesniffer-standard", 374 | "notification-url": "https://packagist.org/downloads/", 375 | "license": [ 376 | "LGPL-3.0-or-later" 377 | ], 378 | "authors": [ 379 | { 380 | "name": "Wim Godden", 381 | "role": "lead" 382 | }, 383 | { 384 | "name": "Juliette Reinders Folmer", 385 | "role": "lead" 386 | } 387 | ], 388 | "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", 389 | "homepage": "http://phpcompatibility.com/", 390 | "keywords": [ 391 | "compatibility", 392 | "phpcs", 393 | "standards", 394 | "static analysis", 395 | "wordpress" 396 | ], 397 | "support": { 398 | "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", 399 | "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" 400 | }, 401 | "time": "2022-10-24T09:00:36+00:00" 402 | }, 403 | { 404 | "name": "phpcsstandards/phpcsextra", 405 | "version": "1.2.1", 406 | "source": { 407 | "type": "git", 408 | "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", 409 | "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489" 410 | }, 411 | "dist": { 412 | "type": "zip", 413 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", 414 | "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", 415 | "shasum": "" 416 | }, 417 | "require": { 418 | "php": ">=5.4", 419 | "phpcsstandards/phpcsutils": "^1.0.9", 420 | "squizlabs/php_codesniffer": "^3.8.0" 421 | }, 422 | "require-dev": { 423 | "php-parallel-lint/php-console-highlighter": "^1.0", 424 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 425 | "phpcsstandards/phpcsdevcs": "^1.1.6", 426 | "phpcsstandards/phpcsdevtools": "^1.2.1", 427 | "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" 428 | }, 429 | "type": "phpcodesniffer-standard", 430 | "extra": { 431 | "branch-alias": { 432 | "dev-stable": "1.x-dev", 433 | "dev-develop": "1.x-dev" 434 | } 435 | }, 436 | "notification-url": "https://packagist.org/downloads/", 437 | "license": [ 438 | "LGPL-3.0-or-later" 439 | ], 440 | "authors": [ 441 | { 442 | "name": "Juliette Reinders Folmer", 443 | "homepage": "https://github.com/jrfnl", 444 | "role": "lead" 445 | }, 446 | { 447 | "name": "Contributors", 448 | "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" 449 | } 450 | ], 451 | "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", 452 | "keywords": [ 453 | "PHP_CodeSniffer", 454 | "phpcbf", 455 | "phpcodesniffer-standard", 456 | "phpcs", 457 | "standards", 458 | "static analysis" 459 | ], 460 | "support": { 461 | "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", 462 | "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", 463 | "source": "https://github.com/PHPCSStandards/PHPCSExtra" 464 | }, 465 | "funding": [ 466 | { 467 | "url": "https://github.com/PHPCSStandards", 468 | "type": "github" 469 | }, 470 | { 471 | "url": "https://github.com/jrfnl", 472 | "type": "github" 473 | }, 474 | { 475 | "url": "https://opencollective.com/php_codesniffer", 476 | "type": "open_collective" 477 | } 478 | ], 479 | "time": "2023-12-08T16:49:07+00:00" 480 | }, 481 | { 482 | "name": "phpcsstandards/phpcsutils", 483 | "version": "1.0.9", 484 | "source": { 485 | "type": "git", 486 | "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", 487 | "reference": "908247bc65010c7b7541a9551e002db12e9dae70" 488 | }, 489 | "dist": { 490 | "type": "zip", 491 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/908247bc65010c7b7541a9551e002db12e9dae70", 492 | "reference": "908247bc65010c7b7541a9551e002db12e9dae70", 493 | "shasum": "" 494 | }, 495 | "require": { 496 | "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", 497 | "php": ">=5.4", 498 | "squizlabs/php_codesniffer": "^3.8.0 || 4.0.x-dev@dev" 499 | }, 500 | "require-dev": { 501 | "ext-filter": "*", 502 | "php-parallel-lint/php-console-highlighter": "^1.0", 503 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 504 | "phpcsstandards/phpcsdevcs": "^1.1.6", 505 | "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0" 506 | }, 507 | "type": "phpcodesniffer-standard", 508 | "extra": { 509 | "branch-alias": { 510 | "dev-stable": "1.x-dev", 511 | "dev-develop": "1.x-dev" 512 | } 513 | }, 514 | "autoload": { 515 | "classmap": [ 516 | "PHPCSUtils/" 517 | ] 518 | }, 519 | "notification-url": "https://packagist.org/downloads/", 520 | "license": [ 521 | "LGPL-3.0-or-later" 522 | ], 523 | "authors": [ 524 | { 525 | "name": "Juliette Reinders Folmer", 526 | "homepage": "https://github.com/jrfnl", 527 | "role": "lead" 528 | }, 529 | { 530 | "name": "Contributors", 531 | "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" 532 | } 533 | ], 534 | "description": "A suite of utility functions for use with PHP_CodeSniffer", 535 | "homepage": "https://phpcsutils.com/", 536 | "keywords": [ 537 | "PHP_CodeSniffer", 538 | "phpcbf", 539 | "phpcodesniffer-standard", 540 | "phpcs", 541 | "phpcs3", 542 | "standards", 543 | "static analysis", 544 | "tokens", 545 | "utility" 546 | ], 547 | "support": { 548 | "docs": "https://phpcsutils.com/", 549 | "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", 550 | "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", 551 | "source": "https://github.com/PHPCSStandards/PHPCSUtils" 552 | }, 553 | "funding": [ 554 | { 555 | "url": "https://github.com/PHPCSStandards", 556 | "type": "github" 557 | }, 558 | { 559 | "url": "https://github.com/jrfnl", 560 | "type": "github" 561 | }, 562 | { 563 | "url": "https://opencollective.com/php_codesniffer", 564 | "type": "open_collective" 565 | } 566 | ], 567 | "time": "2023-12-08T14:50:00+00:00" 568 | }, 569 | { 570 | "name": "squizlabs/php_codesniffer", 571 | "version": "3.9.0", 572 | "source": { 573 | "type": "git", 574 | "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", 575 | "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" 576 | }, 577 | "dist": { 578 | "type": "zip", 579 | "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", 580 | "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", 581 | "shasum": "" 582 | }, 583 | "require": { 584 | "ext-simplexml": "*", 585 | "ext-tokenizer": "*", 586 | "ext-xmlwriter": "*", 587 | "php": ">=5.4.0" 588 | }, 589 | "require-dev": { 590 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" 591 | }, 592 | "bin": [ 593 | "bin/phpcbf", 594 | "bin/phpcs" 595 | ], 596 | "type": "library", 597 | "extra": { 598 | "branch-alias": { 599 | "dev-master": "3.x-dev" 600 | } 601 | }, 602 | "notification-url": "https://packagist.org/downloads/", 603 | "license": [ 604 | "BSD-3-Clause" 605 | ], 606 | "authors": [ 607 | { 608 | "name": "Greg Sherwood", 609 | "role": "Former lead" 610 | }, 611 | { 612 | "name": "Juliette Reinders Folmer", 613 | "role": "Current lead" 614 | }, 615 | { 616 | "name": "Contributors", 617 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" 618 | } 619 | ], 620 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 621 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 622 | "keywords": [ 623 | "phpcs", 624 | "standards", 625 | "static analysis" 626 | ], 627 | "support": { 628 | "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", 629 | "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", 630 | "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 631 | "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" 632 | }, 633 | "funding": [ 634 | { 635 | "url": "https://github.com/PHPCSStandards", 636 | "type": "github" 637 | }, 638 | { 639 | "url": "https://github.com/jrfnl", 640 | "type": "github" 641 | }, 642 | { 643 | "url": "https://opencollective.com/php_codesniffer", 644 | "type": "open_collective" 645 | } 646 | ], 647 | "time": "2024-02-16T15:06:51+00:00" 648 | }, 649 | { 650 | "name": "wp-coding-standards/wpcs", 651 | "version": "3.0.1", 652 | "source": { 653 | "type": "git", 654 | "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", 655 | "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1" 656 | }, 657 | "dist": { 658 | "type": "zip", 659 | "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1", 660 | "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1", 661 | "shasum": "" 662 | }, 663 | "require": { 664 | "ext-filter": "*", 665 | "ext-libxml": "*", 666 | "ext-tokenizer": "*", 667 | "ext-xmlreader": "*", 668 | "php": ">=5.4", 669 | "phpcsstandards/phpcsextra": "^1.1.0", 670 | "phpcsstandards/phpcsutils": "^1.0.8", 671 | "squizlabs/php_codesniffer": "^3.7.2" 672 | }, 673 | "require-dev": { 674 | "php-parallel-lint/php-console-highlighter": "^1.0.0", 675 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 676 | "phpcompatibility/php-compatibility": "^9.0", 677 | "phpcsstandards/phpcsdevtools": "^1.2.0", 678 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" 679 | }, 680 | "suggest": { 681 | "ext-iconv": "For improved results", 682 | "ext-mbstring": "For improved results" 683 | }, 684 | "type": "phpcodesniffer-standard", 685 | "notification-url": "https://packagist.org/downloads/", 686 | "license": [ 687 | "MIT" 688 | ], 689 | "authors": [ 690 | { 691 | "name": "Contributors", 692 | "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" 693 | } 694 | ], 695 | "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", 696 | "keywords": [ 697 | "phpcs", 698 | "standards", 699 | "static analysis", 700 | "wordpress" 701 | ], 702 | "support": { 703 | "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", 704 | "source": "https://github.com/WordPress/WordPress-Coding-Standards", 705 | "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" 706 | }, 707 | "funding": [ 708 | { 709 | "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406", 710 | "type": "custom" 711 | } 712 | ], 713 | "time": "2023-09-14T07:06:09+00:00" 714 | }, 715 | { 716 | "name": "wpscholar/phpcs-standards-wpscholar", 717 | "version": "1.0.6", 718 | "source": { 719 | "type": "git", 720 | "url": "https://github.com/wpscholar/phpcs-standards-wpscholar.git", 721 | "reference": "f25cbbf74cf3c0324efb2dd65339274d4f2d7de5" 722 | }, 723 | "dist": { 724 | "type": "zip", 725 | "url": "https://api.github.com/repos/wpscholar/phpcs-standards-wpscholar/zipball/f25cbbf74cf3c0324efb2dd65339274d4f2d7de5", 726 | "reference": "f25cbbf74cf3c0324efb2dd65339274d4f2d7de5", 727 | "shasum": "" 728 | }, 729 | "require": { 730 | "dealerdirect/phpcodesniffer-composer-installer": "@stable", 731 | "phpcompatibility/phpcompatibility-wp": "@stable", 732 | "squizlabs/php_codesniffer": "@stable", 733 | "wp-coding-standards/wpcs": "@stable" 734 | }, 735 | "type": "phpcodesniffer-standard", 736 | "notification-url": "https://packagist.org/downloads/", 737 | "license": [ 738 | "GPL-2.0-or-later" 739 | ], 740 | "authors": [ 741 | { 742 | "name": "Micah Wood", 743 | "email": "micah@wpscholar.com" 744 | } 745 | ], 746 | "description": "PHP Code Sniffer Standards for WP Scholar", 747 | "support": { 748 | "issues": "https://github.com/wpscholar/phpcs-standards-wpscholar/issues", 749 | "source": "https://github.com/wpscholar/phpcs-standards-wpscholar/tree/1.0.6" 750 | }, 751 | "time": "2023-06-16T13:33:40+00:00" 752 | } 753 | ], 754 | "aliases": [], 755 | "minimum-stability": "stable", 756 | "stability-flags": [], 757 | "prefer-stable": false, 758 | "prefer-lowest": false, 759 | "platform": [], 760 | "platform-dev": [], 761 | "plugin-api-version": "2.6.0" 762 | } 763 | --------------------------------------------------------------------------------