├── .github └── workflows │ └── php.yml ├── .gitignore ├── README.md ├── composer.json ├── php ├── composerpress.php ├── model.php ├── plugin │ ├── gitplugin.php │ ├── hgplugin.php │ ├── plugininterface.php │ ├── svnplugin.php │ ├── wordpressplugin.php │ └── wpackagistplugin.php └── toolpage.php └── plugin.php /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Linting 2 | 3 | on: [push] 4 | 5 | jobs: 6 | lint: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | php: [7.4, 8.1, 8.2] 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: PHP Version 16 | run: php --version 17 | 18 | - name: Lint PHP Files 19 | run: find . -name "*.php" -print0 | xargs -0 -n1 php -l 20 | 21 | - name: Lint PHP Files ( 8 threads ) 22 | run: find . -name "*.php" -print0 | xargs -0 -n1 -P8 php -l 23 | 24 | - name: Validate composer.json and composer.lock 25 | run: ls composer.json > /dev/null && composer validate 26 | 27 | - name: Composer Install 28 | run: ls composer.json > /dev/null && composer install 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Composerpress 2 | 3 | Retroactively attempts to create a `composer.json` for a WordPress site. On activation, a `composer.json` menu appears in the admin area under the Tools menu. This page shows what composerpress thinks a `composer.json` that will generate a website close or identical to the site the plugin is installed on might look like. 4 | 5 | Includes support for detecting/reverse engineering composer packages via: 6 | 7 | - [packagist](https://packagist.org/) 8 | - [wpackagist](https://wpackagist.org/)* 9 | - private [mercurial](https://en.wikipedia.org/wiki/Mercurial) repositories 10 | - private [git](https://en.wikipedia.org/wiki/Git) repositories 11 | - private [svn](https://en.wikipedia.org/wiki/Apache_Subversion) repositories 12 | - non-composer plugins residing in git/svn/mercurial repositories 13 | 14 | --- 15 | 16 | ### Installation Notes 17 | 18 | Install as a regular plugin and then run `composer install` on the `wp-content/plugins/composerpress` plugin folder to install dependencies. 19 | 20 | For instructions on installing composer: see here for *nix and here for Windows 21 | 22 | --- 23 | 24 | ## TODOs: 25 | 26 | Currently ComposerPress does not perform any checking with wpackagist [#2](https://github.com/tomjn/composerpress/issues/2) (or similaries), leaving open these problems: 27 | 28 | - when a _TextDomain_ doesn't match a _Plugins Name_ ? 29 | - when a plugin doesn't match its _Folder Name_ ? 30 | - when a _Plugin is Custom to a site_ (ie doesn't exist in the wp.org repo)? 31 | 32 | ### Keep in mind 33 | 34 | 1. At present, if composerpress can not find the source of a plugin, the choice will fall back on the default "composerpress" (to help you recognize them and act accordingly) 35 | 36 | 2. If you encounter a false positive try to contact plugin's author and ask them to insert, at least a `Plugin URI:` in the header of the plugin; for example: these two plugins could collide if a URI plugin was not specified: 37 | - `Plugin URI: http://wordpress.org/extend/plugins/wp-less/` that will become something like `wpackagist-plugin/wp-less` 38 | - `Plugin URI: https://github.com/sanchothefat/wp-less/` that will become something like `sanchothefat/wp-less` 39 | 40 | 3. You can always give an help and contribute... :smile: 41 | 42 | ### Author articles 43 | 44 | - [ComposerPress "Brief"](https://tomjn.com/2013/10/01/composerpress/) 45 | - [Wordpress & Composer TLDR](https://tomjn.com/2015/09/03/wordpress-and-composer-tldr/) 46 | - [ComposerPress "Project"](https://tomjn.com/projects/composerpress/) 47 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tomjn/composerpress", 3 | "description": "Takes stock of a local install and generates a composer.json", 4 | "license": "GPL-2.0+", 5 | "type": "wordpress-plugin", 6 | "authors": [ 7 | { 8 | "name": "Tom J Nowell", 9 | "email": "contact@tomjn.com" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "require": { 14 | "composer/installers": "^1.0 || ^2.0", 15 | "php": ">=7.4", 16 | "ext-pdo": "*", 17 | "gitonomy/gitlib": "dev-main", 18 | "symfony/console": "^2.3.5", 19 | "composer/composer": "*" 20 | }, 21 | "config": { 22 | "allow-plugins": { 23 | "composer/installers": true 24 | } 25 | }, 26 | "autoload" : { 27 | "classmap": [ 28 | "php/" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /php/composerpress.php: -------------------------------------------------------------------------------- 1 | model = $model; 17 | } 18 | 19 | public function run() { 20 | // 21 | } 22 | 23 | public function fill_model() { 24 | $plugins = get_plugins(); 25 | 26 | $this->model->required( 'johnpbloch/wordpress', '*@stable' ); 27 | $this->model->required( 'php', '>=5.3.2' ); 28 | 29 | $this->model->set_name( 'wpsite/'.sanitize_title( get_bloginfo( 'name' ) ) ); 30 | $this->model->set_homepage( home_url() ); 31 | $description = get_bloginfo( 'description' ); 32 | $this->model->set_description( $description ); 33 | $this->model->set_license( 'GPL-2.0+' ); 34 | $this->model->set_version( get_bloginfo( 'version' ) ); 35 | 36 | $this->model->add_repository( 'composer', 'http://wpackagist.org' ); 37 | 38 | foreach ( $plugins as $key => $plugin_data ) { 39 | if ( !is_plugin_active( $key ) ) { 40 | continue; 41 | } 42 | $path = plugin_dir_path( $key ); 43 | $fullpath = WP_CONTENT_DIR.'/plugins/'.$path; 44 | $filepath = WP_CONTENT_DIR.'/plugins/'.$key; 45 | $plugin = null; 46 | if ( file_exists( $fullpath.'.hg/' ) ) { 47 | $plugin = new HGPlugin( $fullpath, $filepath, $plugin_data ); 48 | } else if ( file_exists( $fullpath.'.git/' ) ) { 49 | $plugin = new GitPlugin( $fullpath, $filepath, $plugin_data ); 50 | } else if ( file_exists( $fullpath.'.svn/' ) ) { 51 | $plugin = new SVNPlugin( $fullpath, $filepath, $plugin_data ); 52 | } else { 53 | $plugin = new WPackagistPlugin( $fullpath, $filepath, $plugin_data ); 54 | } 55 | if ( $plugin != null ) { 56 | $this->model->add_plugin( $plugin ); 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * Retrieve a setting. 63 | * 64 | * @param string $key Setting name. 65 | * @param mixed $default Optional. Default setting value. 66 | * @return mixed 67 | */ 68 | public static function get_setting( $key, $default = false ) { 69 | $option = get_option( 'composerpress' ); 70 | return (isset( $option[ $key ] ) && strlen($option[ $key ]) > 0) ? $option[ $key ] : $default; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /php/model.php: -------------------------------------------------------------------------------- 1 | required = array(); 18 | $this->repos = array(); 19 | $this->extra = array(); 20 | $this->homepage = ''; 21 | $this->description = ''; 22 | $this->version = ''; 23 | $this->name = ''; 24 | $this->license = ''; 25 | } 26 | 27 | public function set_homepage( $homepage ) { 28 | $this->homepage = $homepage; 29 | } 30 | 31 | public function set_name( $name ) { 32 | $this->name = $name; 33 | } 34 | 35 | public function set_version( $version ) { 36 | $this->version = $version; 37 | } 38 | 39 | public function set_description( $description ) { 40 | $this->description = $description; 41 | } 42 | 43 | public function set_license( $license ) { 44 | $this->license = $license; 45 | } 46 | 47 | public function add_repository( $type, $url, $reference = '' ) { 48 | $source = array( 49 | 'type' => $type, 50 | 'url' => $url.$reference 51 | ); 52 | 53 | /*if ( !empty( $reference ) ) { 54 | $source['reference'] = trailingslashit( $reference ); 55 | }*/ 56 | $this->repos[] = $source; 57 | } 58 | 59 | public function add_package_repository( $package ) { 60 | $this->repos[] = array( 61 | 'type' => 'package', 62 | 'package' => $package 63 | ); 64 | } 65 | 66 | public function add_extra( $name, $data ) { 67 | $this->extra[$name] = $data; 68 | } 69 | 70 | public function required( $package, $version ) { 71 | $this->required[ $package ] = $version; 72 | } 73 | 74 | public function add_plugin( \Tomjn\ComposerPress\Plugin\PluginInterface $plugin ) { 75 | $remote_url = $plugin->get_url(); 76 | $reference = $plugin->get_reference(); 77 | $reponame = $plugin->get_name(); 78 | $version = $plugin->get_version(); 79 | $required_version = $plugin->get_required_version(); 80 | if ( empty( $version ) ) { 81 | $required_version = 'dev-master'; 82 | } 83 | $vcstype = $plugin->get_vcs_type(); 84 | 85 | if ( !$plugin->is_packagist() ) { 86 | if ( $plugin->has_composer() ) { 87 | $this->add_repository( $vcstype, $remote_url, $reference ); 88 | if ( !empty( $version ) ) { 89 | $version = $required_version; 90 | } 91 | $this->required( $reponame, $version ); 92 | } else { 93 | $source = array( 94 | 'url' => $remote_url, 95 | 'type' => $vcstype 96 | ); 97 | $source['reference'] = $reference; 98 | $package = array( 99 | 'name' => $reponame, 100 | 'version' => 'dev-master', 101 | 'type' => 'wordpress-plugin', 102 | 'source' => $source 103 | ); 104 | $this->add_package_repository( $package ); 105 | $this->required( $reponame, 'dev-master' ); 106 | } 107 | } else { 108 | $this->required( $reponame, $version ); 109 | } 110 | } 111 | 112 | public function to_json() { 113 | $manifest = array(); 114 | $manifest['name'] = $this->name; 115 | if ( !empty( $this->description ) ) { 116 | $manifest['description'] = $this->description; 117 | } 118 | if ( !empty( $this->license ) ) { 119 | $manifest['license'] = $this->license; 120 | } 121 | if ( !empty( $this->homepage ) ) { 122 | $manifest['homepage'] = $this->homepage; 123 | } 124 | $manifest['version'] = $this->version; 125 | if ( !empty( $this->repos ) ) { 126 | $manifest['repositories'] = $this->repos; 127 | } 128 | if ( !empty( $this->extra ) ) { 129 | $manifest['extra'] = $this->extra; 130 | } 131 | if ( !empty( $this->required ) ) { 132 | $manifest['require'] = $this->required; 133 | } 134 | $manifest['minimum-stability'] = 'dev'; 135 | $json = json_encode( $manifest, ( JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ) ); 136 | return $json; 137 | } 138 | } -------------------------------------------------------------------------------- /php/plugin/gitplugin.php: -------------------------------------------------------------------------------- 1 | repository = new Repository( $this->path ); 13 | } 14 | 15 | public function get_version() { 16 | $version = $this->plugin_data['Version']; 17 | if ( $this->has_composer() ) { 18 | $composer = $this->get_composer(); 19 | if ( !empty( $composer->version ) ) { 20 | return $composer->version; 21 | } 22 | } 23 | return $version; 24 | } 25 | 26 | public function get_required_version() { 27 | $version = '>='.$this->plugin_data['Version']; 28 | if ( $this->has_composer() ) { 29 | $composer = $this->get_composer(); 30 | if ( !empty( $composer->version ) ) { 31 | if ( is_numeric( $composer->version ) ) 32 | return '~'.$composer->version; 33 | return $composer->version; 34 | } 35 | } 36 | return $version; 37 | } 38 | 39 | public function is_packagist() { 40 | return false; 41 | } 42 | 43 | public function has_vcs() { 44 | return true; 45 | } 46 | 47 | public function get_vcs_type() { 48 | return 'git'; 49 | } 50 | 51 | public function get_url() { 52 | if ( $this->has_composer() ) { 53 | //wp_die( 'omg composer'.$this->get_name() ); 54 | } 55 | // get the repository URL 56 | $remote_url = $this->repository->run( 'config', array( 57 | '--get' => 'remote.origin.url' 58 | ) ); 59 | $remote_url = trim( $remote_url ); 60 | return $remote_url; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /php/plugin/hgplugin.php: -------------------------------------------------------------------------------- 1 | plugin_data['Version']; 14 | if ( $this->has_composer() ) { 15 | $composer = $this->get_composer(); 16 | if ( !empty( $composer->version ) ) { 17 | return $composer->version; 18 | } 19 | } 20 | return $version; 21 | } 22 | 23 | public function get_required_version() { 24 | $version = '>='.$this->plugin_data['Version']; 25 | if ( $this->has_composer() ) { 26 | $composer = $this->get_composer(); 27 | if ( !empty( $composer->version ) ) { 28 | return $composer->version; 29 | } 30 | } 31 | return $version; 32 | } 33 | 34 | public function is_packagist() { 35 | return false; 36 | } 37 | 38 | public function has_vcs() { 39 | return true; 40 | } 41 | 42 | public function get_vcs_type() { 43 | return 'hg'; 44 | } 45 | 46 | public function get_url() { 47 | $hgrcpath = trailingslashit( $this->path ).'.hg/hgrc'; 48 | $hgrc = parse_ini_file( $hgrcpath ); 49 | $remote_url = $hgrc['default']; 50 | 51 | $remote_url = trim( $remote_url ); 52 | return $remote_url; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /php/plugin/plugininterface.php: -------------------------------------------------------------------------------- 1 | plugin_data['Version']; 12 | if ( $this->has_composer() ) { 13 | $composer = $this->get_composer(); 14 | if ( !empty( $composer->version ) ) { 15 | return $composer->version; 16 | } 17 | } 18 | return $version; 19 | } 20 | 21 | public function get_required_version() { 22 | $version = '>='.$this->plugin_data['Version']; 23 | if ( $this->has_composer() ) { 24 | $composer = $this->get_composer(); 25 | if ( !empty( $composer->version ) ) { 26 | return $composer->version; 27 | } 28 | } 29 | return $version; 30 | } 31 | 32 | public function is_packagist() { 33 | return false; 34 | } 35 | 36 | public function get_vcs_type() { 37 | return 'svn'; 38 | } 39 | 40 | public function get_url() { 41 | $dbpath = trailingslashit( $this->path ).'.svn/wc.db'; 42 | 43 | $root = ''; 44 | //$database = \sqlite_open( $dbpath, 0666, $error ); 45 | $database = new \PDO( 'sqlite:'.$dbpath ); 46 | $sql = 'SELECT root FROM REPOSITORY ORDER BY id'; 47 | foreach ( $database->query( $sql ) as $row ) { 48 | $root = trailingslashit( $row['root'] ); 49 | break; 50 | } 51 | 52 | //$info = \svn_info( $this->path ); 53 | return $root; 54 | } 55 | 56 | public function get_reference() { 57 | $dbpath = trailingslashit( $this->path ).'.svn/wc.db'; 58 | 59 | //$database = \sqlite_open( $dbpath, 0666, $error ); 60 | $database = new \PDO( 'sqlite:'.$dbpath ); 61 | 62 | $key = substr( $this->filepath, strlen( $this->path ) ); 63 | 64 | $sql = 'SELECT * FROM NODES WHERE local_relpath = "'.$key.'" ORDER BY wc_id'; 65 | foreach ( $database->query( $sql ) as $row ) { 66 | $rel = $row['repos_path']; 67 | break; 68 | } 69 | 70 | return dirname( $rel ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /php/plugin/wordpressplugin.php: -------------------------------------------------------------------------------- 1 | path = $path; 14 | $this->filepath = $filepath; 15 | $this->plugin_data = $plugin_data; 16 | } 17 | 18 | public function get_name() { 19 | 20 | $namespace = ComposerPress::get_setting('vendor', ComposerPress::DEFAULT_FALLBACK_VENDOR ); 21 | $package = basename($this->path); 22 | 23 | $reponame = sanitize_title($namespace).'/'.sanitize_title($package); 24 | if ( $this->has_composer() ) { 25 | $composer = $this->get_composer(); 26 | if ( !empty( $composer->name ) ) { 27 | return $composer->name; 28 | } 29 | } 30 | return $reponame; 31 | } 32 | 33 | abstract public function get_version(); 34 | abstract public function get_required_version(); 35 | 36 | abstract public function is_packagist(); 37 | 38 | public function has_composer() { 39 | $path = trailingslashit( $this->path ).'composer.json'; 40 | return file_exists( $path ); 41 | } 42 | 43 | public function get_composer() { 44 | $path = trailingslashit( $this->path ).'composer.json'; 45 | $content = file_get_contents( $path ); 46 | $json = json_decode( $content ); 47 | //wp_die( print_r( $json, true ) ); 48 | return $json; 49 | } 50 | 51 | abstract public function get_vcs_type(); 52 | abstract public function get_url(); 53 | public function get_reference(){ 54 | return ''; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /php/plugin/wpackagistplugin.php: -------------------------------------------------------------------------------- 1 | plugin_data['Version']; 13 | if ( $this->has_composer() ) { 14 | $composer = $this->get_composer(); 15 | if ( !empty( $composer->version ) ) { 16 | return $composer->version; 17 | } 18 | } 19 | return $version; 20 | } 21 | 22 | public function get_required_version() { 23 | $version = '>='.$this->plugin_data['Version']; 24 | if ( $this->has_composer() ) { 25 | $composer = $this->get_composer(); 26 | if ( !empty( $composer->version ) ) { 27 | return $composer->version; 28 | } 29 | } 30 | return $version; 31 | } 32 | 33 | public function is_packagist() { 34 | return true; 35 | } 36 | 37 | public function get_vcs_type() { 38 | return 'composer'; 39 | } 40 | 41 | public function get_url() { 42 | return 'https://wpackagist.org'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /php/toolpage.php: -------------------------------------------------------------------------------- 1 | composerpress = $composerpress; 12 | $this->model = $model; 13 | 14 | add_action( 'admin_menu', array( $this, 'on_admin_menu' ) ); 15 | add_action( 'admin_init', array( $this, 'on_admin_init' ) ); 16 | } 17 | 18 | 19 | public function on_admin_menu() { 20 | add_submenu_page( 'tools.php', 'Composer.json', 'Composer.json', 'manage_options', 'composer-json-page', array( $this, 'options_page' ) ); 21 | } 22 | 23 | public function on_admin_init() { 24 | // Composerpress custom settings 25 | $this->register_settings(); 26 | $this->add_sections(); 27 | $this->add_settings(); 28 | } 29 | 30 | /** 31 | * Register settings. 32 | */ 33 | public function register_settings() { 34 | register_setting( 'composerpress', 'composerpress', array( $this, 'sanitize_settings' ) ); 35 | } 36 | 37 | /** 38 | * Add settings sections. 39 | */ 40 | public function add_sections() { 41 | add_settings_section( 42 | 'default', 43 | __( 'Settings', 'composerpress' ), 44 | '__return_null', 45 | 'composerpress' 46 | ); 47 | } 48 | 49 | /** 50 | * Register individual settings. 51 | */ 52 | public function add_settings() { 53 | add_settings_field( 54 | 'vendor', 55 | __( 'Fallback Vendor', 'composerpress' ), 56 | array( $this, 'render_field_vendor' ), 57 | 'composerpress', 58 | 'default' 59 | ); 60 | } 61 | 62 | 63 | /** 64 | * Display a field for defining the vendor. 65 | */ 66 | public function render_field_vendor() { 67 | $value = $this->composerpress->get_setting( 'vendor', '' ); 68 | ?> 69 |
70 |
71 | Default is composerpress
72 |
'; 102 | echo $this->model->to_json(); 103 | echo ''; 104 | 105 | echo ''; 106 | 107 | // Prints Settings Form 108 | echo ''; 113 | 114 | echo '
composer install
on the wp-content/plugins/'.basename( __DIR__ ).'
plugin folder to install dependencies.For instructions on installing composer:
' ); 37 | } else { 38 | error_log( 'composer install command needs to be ran on this plugin' ); 39 | return; 40 | } 41 | 42 | $model = new Model(); 43 | $composerplugin = new ComposerPress( $model ); 44 | $toolpage = new ToolPage( $composerplugin, $model ); 45 | $composerplugin->run(); 46 | --------------------------------------------------------------------------------