├── .editorconfig ├── .github └── workflows │ └── releases.yml ├── .gitignore ├── CHANGELOG.md ├── LICENCE.md ├── README.md ├── Tests ├── BaseTestCase.php └── Commands │ └── Common │ ├── NewCommandTest.php │ └── StatusCommandTest.php ├── app ├── version.json └── wire-cli.php ├── autoload.php ├── commitlint.config.js ├── composer.json ├── composer.lock ├── docs ├── assets │ ├── capture-cmd.jpg │ └── illustration.png └── commands │ ├── backup.md │ ├── common.md │ ├── field.md │ ├── log.md │ ├── module.md │ ├── page.md │ ├── role.md │ ├── template.md │ └── user.md ├── package-lock.json ├── package.json ├── phpunit.xml.dist ├── src ├── Commands │ ├── Backup │ │ ├── BackupDatabaseCommand.php │ │ ├── BackupDuplicatorCommand.php │ │ ├── BackupImagesCommand.php │ │ ├── Duplicator │ │ │ ├── DuplicatorListCommand.php │ │ │ └── DuplicatorNewPackageCommand.php │ │ └── RestoreDatabaseCommand.php │ ├── Common │ │ ├── CheatCommand.php │ │ ├── DebugCommand.php │ │ ├── NewCommand.php │ │ ├── ServeCommand.php │ │ ├── StatusCommand.php │ │ ├── UpgradeCommand.php │ │ └── ViteCommand.php │ ├── Field │ │ ├── FieldCloneCommand.php │ │ ├── FieldCreateCommand.php │ │ ├── FieldDeleteCommand.php │ │ ├── FieldEditCommand.php │ │ ├── FieldListCommand.php │ │ ├── FieldRenameCommand.php │ │ ├── FieldTagCommand.php │ │ └── FieldTypesCommand.php │ ├── Logs │ │ ├── LogListCommand.php │ │ └── LogTailCommand.php │ ├── Module │ │ ├── ModuleDisableCommand.php │ │ ├── ModuleDownloadCommand.php │ │ ├── ModuleEnableCommand.php │ │ ├── ModuleGenerateCommand.php │ │ └── ModuleUpgradeCommand.php │ ├── Page │ │ ├── PageCreateCommand.php │ │ ├── PageDeleteCommand.php │ │ ├── PageEmptyTrashCommand.php │ │ └── PageListCommand.php │ ├── Role │ │ ├── RoleCreateCommand.php │ │ ├── RoleDeleteCommand.php │ │ └── RoleListCommand.php │ ├── Template │ │ ├── TemplateCreateCommand.php │ │ ├── TemplateDeleteCommand.php │ │ ├── TemplateFieldsCommand.php │ │ ├── TemplateInfoCommand.php │ │ ├── TemplateListCommand.php │ │ └── TemplateTagCommand.php │ └── User │ │ ├── UserCreateCommand.php │ │ ├── UserDeleteCommand.php │ │ ├── UserListCommand.php │ │ └── UserUpdateCommand.php └── Helpers │ ├── Downloader.php │ ├── Installer.php │ ├── ProcessDiagnostics │ ├── DiagnoseImagehandling.php │ ├── DiagnosePhp.php │ └── ProcessDiagnostics.php │ ├── PwConnector.php │ ├── PwModuleTools.php │ ├── PwTools.php │ ├── PwUserTools.php │ ├── WsTables.php │ └── WsTools.php └── wirecli /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | charset = utf-8 10 | end_of_line = lf 11 | indent_size = 2 12 | indent_style = space 13 | 14 | [*.{php,inc,module,json}] 15 | indent_style = space 16 | trim_trailing_whitespace = false 17 | insert_final_newline = true 18 | 19 | 20 | [*.js] 21 | indent_style = space 22 | trim_trailing_whitespace = false 23 | insert_final_newline = true 24 | 25 | 26 | [*.{css,less,scss}] 27 | indent_style = space 28 | trim_trailing_whitespace = false 29 | insert_final_newline = true -------------------------------------------------------------------------------- /.github/workflows/releases.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/release.yml 2 | name: Release 3 | on: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | release: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - name: Checkout source code 12 | uses: actions/checkout@v2 13 | - name: Install the dependancies 14 | run: npm ci 15 | - name: Initialize Git user 16 | run: | 17 | git config --global user.email "code@sekretservices.com" 18 | git config --global user.name "Release Workflow 🤖" 19 | - name: Log git status 20 | run: git status 21 | - name: Run release 22 | run: npm run release --ci 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | - name: Generate Contributors Images 26 | uses: jaywcjlove/github-action-contributors@main 27 | id: contributors 28 | with: 29 | filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\]) 30 | avatarSize: 32 31 | - name: Modify README.md 32 | uses: jaywcjlove/github-action-modify-file-content@main 33 | with: 34 | path: README.md 35 | body: '${{steps.contributors.outputs.htmlList}}' 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer 2 | vendor 3 | 4 | # Binaries 5 | composer.phar 6 | 7 | # Node modules 8 | node_modules 9 | 10 | # Tests 11 | phpunit.xml 12 | 13 | # Directories etc. 14 | ProcessWire 15 | Tests/processwire.zip 16 | 17 | Tests/pw 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.4.12 (2024-09-07) 4 | 5 | ## 6 | 7 | All notable changes to this project will be documented in this file. Dates are displayed in UTC. 8 | 9 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 10 | 11 | #### [1.4.12](https://github.com/wirecli/wire-cli/compare/1.4.10...1.4.12) 12 | 13 | - Dev [`#15`](https://github.com/wirecli/wire-cli/pull/15) 14 | 15 | #### [1.4.10](https://github.com/wirecli/wire-cli/compare/1.4.9...1.4.10) 16 | 17 | > 27 October 2023 18 | 19 | - Fix config [`#10`](https://github.com/wirecli/wire-cli/pull/10) 20 | - feat(cmd-serve): improving the flexibility of the `serve` command with port detection [`932a3ab`](https://github.com/wirecli/wire-cli/commit/932a3ab75ebed03e70347434ba635f2aac16c12e) 21 | - fix(config): Added missing `installed`, `authSalt` and `tableSalt` config #9 [`d250942`](https://github.com/wirecli/wire-cli/commit/d250942eb160161be2240b6c439198dfdb4102bd) 22 | - chore(release): %s [`d556353`](https://github.com/wirecli/wire-cli/commit/d5563538dd8d0e1c8e360709f152f867af14981c) 23 | 24 | #### [1.4.9](https://github.com/wirecli/wire-cli/compare/1.4.8...1.4.9) 25 | 26 | > 20 July 2023 27 | 28 | - Dev: serve command enhancement [`#7`](https://github.com/wirecli/wire-cli/pull/7) 29 | - feat(cmd-serve): improving the flexibility of the `serve` command with port detection [`814bd79`](https://github.com/wirecli/wire-cli/commit/814bd799f875c1ff418fbf2b70d5610bf9b5d822) 30 | - chore(release): %s [`35a0998`](https://github.com/wirecli/wire-cli/commit/35a0998ff559b2854eaf0ca623929bb952a3831c) 31 | - chore(workflow): run workflow on push event [`15f5522`](https://github.com/wirecli/wire-cli/commit/15f55229c331de0a8d2173764f2394f8854a3d1b) 32 | 33 | #### [1.4.8](https://github.com/wirecli/wire-cli/compare/v1.4.6...1.4.8) 34 | 35 | > 15 July 2023 36 | 37 | - Dev [`#5`](https://github.com/wirecli/wire-cli/pull/5) 38 | - docs(ci-cd): fix release action [`#4`](https://github.com/wirecli/wire-cli/pull/4) 39 | - docs(ci-cd): fixed pulled version number from app [`#3`](https://github.com/wirecli/wire-cli/pull/3) 40 | - chore(release): {{currentTag}} [skip ci] [`5c8adfa`](https://github.com/wirecli/wire-cli/commit/5c8adfa2d42706056d959dd07855e1c052a27db9) 41 | - chore(release): %s [`970952e`](https://github.com/wirecli/wire-cli/commit/970952e210fd7e56257b3795fe6d14b222476c50) 42 | - docs(readme): updated README [`6a263e3`](https://github.com/wirecli/wire-cli/commit/6a263e3f060a5a84561f6f6fa02223655fdb124c) 43 | 44 | #### v1.4.6 45 | 46 | > 14 July 2023 47 | 48 | - fix(package): fixed composer schema [`#2`](https://github.com/wirecli/wire-cli/pull/2) 49 | - Dev [`#1`](https://github.com/wirecli/wire-cli/pull/1) 50 | - docs: This is not the commit message you are looking for [`6d52151`](https://github.com/wirecli/wire-cli/commit/6d521517ef1e26b96a3a6b61d2e1e619aba9d804) 51 | - docs: add README [`13356ae`](https://github.com/wirecli/wire-cli/commit/13356ae55b8232ba89250f0a5fdbc8ee7560553b) 52 | - docs: Add Github Workflows [`9be02fb`](https://github.com/wirecli/wire-cli/commit/9be02fbf12aa1cfb6d522fa2eb325f26e102ecad) -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![wire-cli illustration](https://github.com/wirecli/wire-cli/blob/main/docs/assets/illustration.png?raw=true) 2 | 3 | # wire-cli 4 | 5 | wire-cli is a CLI (Command-Line Interface) tool designed to provide ease of use and efficiency for ProcessWire developers. With wire-cli, you can automate common tasks, manage ProcessWire projects effortlessly, and enhance your development workflow. 6 | 7 | ## Features 8 | 9 | - Create new ProcessWire projects 10 | - Serve ProcessWire via built-in PHP webserver 11 | - Perform database backup and restoration 12 | - Manage fields, templates, roles, users, and modules 13 | - Generate boilerplate modules 14 | - Check for core upgrades 15 | - And more... 16 | 17 | wire-cli is based on the defunct wireshell, built using the [Symfony Console](https://symfony.com/doc/current/components/console.html) Component, offering a powerful and intuitive command-line interface for interacting with ProcessWire projects and adding new commands. It is compatible with PHP 8.1. 18 | 19 | Please note that wire-cli and another tool called [rockshell](https://github.com/baumrock/rockshell) share similar goals and functionality. In the future, the features of wire-cli and rockshell will be merged to provide a unified and comprehensive CLI tool for ProcessWire developers. 20 | 21 | ## Installation 22 | 23 | To install wire-cli, you need to have Composer installed. Run the following command to install wire-cli globally: 24 | 25 | ``` 26 | composer global require wirecli/wire-cli 27 | ``` 28 | 29 | ## Usage 30 | 31 | Run `wire-cli` followed by the desired command to execute various tasks. For example: 32 | 33 | ``` 34 | wire-cli new myproject 35 | ``` 36 | 37 | For a complete list of available commands and options, use the `help` command: 38 | 39 | ## Documentation 40 | 41 | For detailed documentation and usage examples, please refer to the [official documentation](https://github.com/wirecli/wire-cli). 42 | 43 | Support thread: 44 | https://processwire.com/talk/topic/28788-wire-cli-a-cli-tool-for-processwire-developers/ 45 | 46 | ## Contributing 47 | 48 | Contributions are welcome! If you encounter any issues or have suggestions for improvements, please submit an issue or a pull request on the [GitHub repository](https://github.com/wirecli/wire-cli). 49 | 50 | ## Contributors 51 | 52 | As always, thanks to all the contributors! 53 | 54 | j. eizmendi 55 | 56 | 57 | ## Available commands 58 | 59 | ![Commands preview](https://github.com/wirecli/wire-cli/blob/main/docs/assets/capture-cmd.jpg) 60 | 61 | ## Credits 62 | 63 | wire-cli is inspired by the work of the following authors of the initial developers/contributors of wireshell: 64 | 65 | - Marcus Herrmann 66 | - Hari K T 67 | - Bea David 68 | - Camilo Castro 69 | - Horst Nogajski 70 | 71 | ## License 72 | 73 | This project is licensed under the [MIT License](LICENSE.md). 74 | -------------------------------------------------------------------------------- /Tests/BaseTestCase.php: -------------------------------------------------------------------------------- 1 | fs = new Filesystem(); 29 | $this->app = new Application(); 30 | $this->app->setAutoExit(false); 31 | } 32 | 33 | public function checkInstallation() { 34 | if ($this->fs->exists(self::INSTALLATION_FOLDER)) $this->fs->remove(self::INSTALLATION_FOLDER); 35 | 36 | // if installation exists and zip file is older than 24h remove it 37 | if ($this->fs->exists(self::INSTALLATION_ARCHIVE) && (time() - filemtime(self::INSTALLATION_ARCHIVE)) > 86400) { 38 | $this->fs->remove(self::INSTALLATION_ARCHIVE); 39 | } 40 | 41 | if (!$this->fs->exists(self::INSTALLATION_ARCHIVE)) $this->downloadArchive(); 42 | } 43 | 44 | public function downloadArchive() { 45 | $client = new Client(); 46 | $client->request('GET', 'https://github.com/processwire/processwire/archive/master.zip', ['sink' => 'Tests/processwire.zip']); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Tests/Commands/Common/NewCommandTest.php: -------------------------------------------------------------------------------- 1 | 'Europe\Berlin', 18 | '--httpHosts' => 'wirecli.sek', 19 | '--username' => 'admin', 20 | '--userpass' => 'password', 21 | '--useremail' => 'test@wirecli.pw' 22 | ); 23 | 24 | /** 25 | * @before 26 | */ 27 | public function setupCommand() { 28 | $this->app->add(new NewCommand()); 29 | $this->command = $this->app->find('new'); 30 | $this->tester = new CommandTester($this->command); 31 | 32 | $credentials = array( 33 | '--dbUser' => $GLOBALS['DB_USER'], 34 | '--dbPass' => $GLOBALS['DB_PASSWD'], 35 | '--dbName' => $GLOBALS['DB_DBNAME'] 36 | ); 37 | 38 | $this->extends = array( 39 | 'command' => $this->command->getName(), 40 | 'directory' => Base::INSTALLATION_FOLDER, 41 | ); 42 | 43 | $this->defaults = array_merge($this->defaults, $credentials, $this->extends); 44 | } 45 | 46 | public function testDownload() { 47 | $this->checkInstallation(); 48 | $options = array('--no-install' => true, '--src' => Base::INSTALLATION_ARCHIVE); 49 | $this->tester->execute(array_merge($this->defaults, $options)); 50 | 51 | $this->assertDirectoryExists(Base::INSTALLATION_FOLDER); 52 | $this->assertDirectoryExists(Base::INSTALLATION_FOLDER . '/wire'); 53 | $this->assertDirectoryNotExists(Base::INSTALLATION_FOLDER . '/site'); 54 | } 55 | 56 | /** 57 | * @depends testDownload 58 | * @expectedException RuntimeException 59 | * @expectedExceptionMessageRegExp /(Database connection information did not work)./ 60 | */ 61 | public function testInstallWrongPassword() { 62 | // check ProcessWire has not been installed yet 63 | if ($this->fs->exists(Base::INSTALLATION_FOLDER . '/site/config.php')) return; 64 | 65 | // return the input you want to answer the question with 66 | $this->mockQuestionHelper($this->command, function($text, $order, Question $question) { 67 | if (strpos($text, 'database user name') !== false) return 'whatever'; 68 | if (strpos($text, 'database password') !== false) return 'wrong'; 69 | 70 | throw new UnhandledQuestionException(); 71 | }); 72 | 73 | $options = array( 74 | '--src' => Base::INSTALLATION_ARCHIVE, 75 | '--dbPass' => 'wrong' 76 | ); 77 | 78 | $this->tester->execute(array_merge($this->defaults, $options)); 79 | } 80 | 81 | /** 82 | * @depends testDownload 83 | * @expectedExceptionMessageRegExp /(enter a valid email address)/ 84 | */ 85 | public function testInstallInvalidEmailAddress() { 86 | // check ProcessWire has not been installed yet 87 | if ($this->fs->exists(Base::INSTALLATION_FOLDER . '/site/config.php')) return; 88 | 89 | // return the input you want to answer the question with 90 | $this->mockQuestionHelper($this->command, function($text, $order, Question $question) { 91 | if (strpos($text, 'admin email address') !== false) return 'whatever'; 92 | 93 | throw new UnhandledQuestionException(); 94 | }); 95 | 96 | $options = array( 97 | '--src' => Base::INSTALLATION_ARCHIVE, 98 | '--useremail' => 'invalid' 99 | ); 100 | 101 | $this->tester->execute(array_merge($this->defaults, $options)); 102 | } 103 | 104 | /** 105 | * @depends testDownload 106 | */ 107 | public function testInstall() { 108 | // check ProcessWire has not been installed yet 109 | if ($this->fs->exists(Base::INSTALLATION_FOLDER . '/site/config.php')) return; 110 | 111 | $this->tester->execute($this->defaults); 112 | $output = $this->tester->getDisplay(); 113 | 114 | $this->assertDirectoryExists(Base::INSTALLATION_FOLDER . '/site'); 115 | $this->assertFileExists(Base::INSTALLATION_FOLDER . '/site/config.php'); 116 | $this->assertContains('Congratulations, ProcessWire has been successfully installed.', $output); 117 | } 118 | 119 | /** 120 | * @depends testInstall 121 | * @expectedExceptionMessageRegExp /(There is already a \')(.*)(\' project)/ 122 | */ 123 | public function testIsInstalled() { 124 | $this->tester->execute($this->defaults); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Tests/Commands/Common/StatusCommandTest.php: -------------------------------------------------------------------------------- 1 | app->add(new StatusCommand()); 14 | $this->command = $this->app->find('status'); 15 | $this->tester = new CommandTester($this->command); 16 | } 17 | 18 | public function testNotEmptyOutput() { 19 | $this->tester->execute(array( 20 | 'command' => $this->command->getName() 21 | )); 22 | 23 | $output = $this->tester->getDisplay(); 24 | $this->assertContains('Version', $output); 25 | $this->assertContains('ProcessWire', $output); 26 | $this->assertContains('*****', $output); 27 | } 28 | 29 | public function testImageDiagnostic() { 30 | $this->tester->execute(array( 31 | 'command' => $this->command->getName(), 32 | '--image' => true 33 | )); 34 | 35 | $output = $this->tester->getDisplay(); 36 | $this->assertContains('Image Diagnostics', $output); 37 | } 38 | 39 | public function testPhpDiagnostic() { 40 | $this->tester->execute(array( 41 | 'command' => $this->command->getName(), 42 | '--php' => true 43 | )); 44 | 45 | $output = $this->tester->getDisplay(); 46 | $this->assertContains('PHP Diagnostics', $output); 47 | } 48 | 49 | public function testDisplayPass() { 50 | $this->tester->execute(array( 51 | 'command' => $this->command->getName(), 52 | '--pass' => true 53 | )); 54 | 55 | $output = $this->tester->getDisplay(); 56 | $this->assertNotContains('*****', $output); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.4.12" 3 | } 4 | -------------------------------------------------------------------------------- /app/wire-cli.php: -------------------------------------------------------------------------------- 1 | version]); 59 | $app = new Application('wire-cli - An extendable ProcessWire CLI', $version); 60 | 61 | $app->add(new UserCreateCommand()); 62 | $app->add(new UserUpdateCommand()); 63 | $app->add(new UserDeleteCommand()); 64 | $app->add(new UserListCommand()); 65 | $app->add(new RoleCreateCommand()); 66 | $app->add(new RoleDeleteCommand()); 67 | $app->add(new RoleListCommand()); 68 | $app->add(new TemplateCreateCommand()); 69 | $app->add(new TemplateFieldsCommand()); 70 | $app->add(new TemplateTagCommand()); 71 | $app->add(new TemplateInfoCommand()); 72 | $app->add(new TemplateDeleteCommand()); 73 | $app->add(new TemplateListCommand()); 74 | $app->add(new FieldCreateCommand()); 75 | $app->add(new FieldCloneCommand()); 76 | $app->add(new FieldDeleteCommand()); 77 | $app->add(new FieldTagCommand()); 78 | $app->add(new FieldTypesCommand()); 79 | $app->add(new FieldListCommand()); 80 | $app->add(new FieldEditCommand()); 81 | $app->add(new FieldRenameCommand()); 82 | $app->add(new ModuleDownloadCommand()); 83 | $app->add(new ModuleEnableCommand()); 84 | $app->add(new ModuleDisableCommand()); 85 | $app->add(new ModuleGenerateCommand(new GuzzleHttp\Client())); 86 | $app->add(new ModuleUpgradeCommand()); 87 | $app->add(new NewCommand()); 88 | $app->add(new ViteCommand()); 89 | $app->add(new UpgradeCommand(new \Symfony\Component\Filesystem\Filesystem())); 90 | $app->add(new StatusCommand()); 91 | $app->add(new ServeCommand()); 92 | $app->add(new CheatCommand()); 93 | $app->add(new DebugCommand()); 94 | $app->add(new BackupDatabaseCommand()); 95 | $app->add(new BackupImagesCommand()); 96 | $app->add(new RestoreDatabaseCommand()); 97 | $app->add(new BackupDuplicatorCommand()); 98 | $app->add(new PageCreateCommand()); 99 | $app->add(new PageListCommand()); 100 | $app->add(new PageDeleteCommand()); 101 | $app->add(new PageEmptyTrashCommand()); 102 | $app->add(new LogTailCommand()); 103 | $app->add(new LogListCommand()); 104 | 105 | $app->run(); -------------------------------------------------------------------------------- /autoload.php: -------------------------------------------------------------------------------- 1 | setPsr4('Wirecli\\Tests\\', __DIR__ . '/Tests'); 12 | 13 | return $loader; 14 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {extends: ['@commitlint/config-conventional']} 2 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wirecli/wire-cli", 3 | "description": "An extendable ProcessWire command line interface", 4 | "keywords": [ 5 | "processwire" 6 | ], 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Joani Eizmendi", 11 | "homepage": "https://github.com/flydev-fr" 12 | }, 13 | { 14 | "name": "Marcus Herrmann", 15 | "homepage": "https://bigger-on-the-inside.net" 16 | }, 17 | { 18 | "name": "Hari K T", 19 | "homepage": "http://harikt.com" 20 | }, 21 | { 22 | "name": "Bea David", 23 | "homepage": "http://justonestep.de/" 24 | }, 25 | { 26 | "name": "Camilo Castro", 27 | "homepage": "http://www.cervezapps.cl" 28 | } 29 | ], 30 | "bin": [ 31 | "wirecli" 32 | ], 33 | "autoload": { 34 | "psr-4": { 35 | "Wirecli\\": "src/" 36 | } 37 | }, 38 | "require": { 39 | "php": ">=8.1.0", 40 | "guzzlehttp/guzzle": "~7.7", 41 | "monolog/monolog": "~3.4", 42 | "pmjones/auto-shell": "^1.0.1", 43 | "symfony/console": "^6.3", 44 | "symfony/filesystem": "^6.3", 45 | "symfony/process": "^6.3" 46 | }, 47 | "require-dev": { 48 | "phpunit/phpunit": "^10.2", 49 | "whatthejeff/nyancat-phpunit-resultprinter": "~1.2" 50 | }, 51 | "extra": { 52 | "symfony": { 53 | "require": "5.2.*" 54 | } 55 | }, 56 | "version": "1.4.12" 57 | } 58 | -------------------------------------------------------------------------------- /docs/assets/capture-cmd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wirecli/wire-cli/77661a100f75f96521337bca781ac2d4b0438737/docs/assets/capture-cmd.jpg -------------------------------------------------------------------------------- /docs/assets/illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wirecli/wire-cli/77661a100f75f96521337bca781ac2d4b0438737/docs/assets/illustration.png -------------------------------------------------------------------------------- /docs/commands/backup.md: -------------------------------------------------------------------------------- 1 | ![Wirecli Logo](/assets/img/favicon-16x16.png){.logo} **Backup** 2 | 3 | --- 4 | 5 | ## Database 6 | 7 | Connects a to MySQL database and dumps its complete content into a sql file in the PW installation's root folder. 8 | Defaults to a date-and-time based file name when no file name is provided. 9 | 10 | ```sh 11 | $ wirecli backup:db 12 | ``` 13 | 14 | ### Available options: 15 | 16 | ```sh 17 | --filename : Provide a file name for the dump 18 | --target : Provide a file path for the dump (relative to ProcessWire root directory or absolute) 19 | ``` 20 | 21 | ### Examples 22 | 23 | Dump database into existing folder. 24 | 25 | ```sh 26 | $ wirecli backup:db --filename=ymd-bak --target=db 27 | 28 | Dumped database into `db/ymd-bak.sql` successfully. 29 | ``` 30 | 31 | Dump database into non-existing folder. 32 | 33 | ```sh 34 | $ wirecli backup:db --filename=ymd-bak --target=nonexisting 35 | 36 | Export failed with message: Unable to move the temporary file. Please make sure that the provided target exists. 37 | ``` 38 | 39 | You can use absolute as well as relative paths. 40 | 41 | * db 42 | * "../db" 43 | * /Users/username/Downloads 44 | 45 | ```sh 46 | $ wirecli backup:db --filename=ymd-bak --target="../db" 47 | 48 | Dumped database into `db/ymd-bak.sql` successfully. 49 | ``` 50 | 51 | ## Images 52 | 53 | Performs images backup. 54 | 55 | ```sh 56 | $ wirecli backup:images 57 | ``` 58 | 59 | ### Available options: 60 | 61 | ```sh 62 | --selector : can either be a page name or a page id 63 | --field : refer to the image field that contents will be backupped (defaults to images) 64 | --target : store the backup files into a particular folder 65 | ``` 66 | 67 | ### Examples 68 | 69 | Dump images into a specific folder. 70 | 71 | ```sh 72 | $ wirecli backup:images --target=images 73 | 74 | Dumped 2 images into /Users/username/Projects/pw/images successfully. 75 | ``` 76 | 77 | Dump images that refer to the field `logo`. Provide field and selector. 78 | 79 | ```sh 80 | $ wirecli backup:images --field=logo --selector=1171 81 | 82 | Dumped 2 images into /Users/username/Projects/pw/dump-2015-11-30-09-46-32 successfully. 83 | ``` 84 | 85 | Dump images that refer to a non-existing field `nologo`. 86 | 87 | ```sh 88 | $ wirecli backup:images --field=nologo 89 | 90 | No images found. Recheck your options. 91 | ``` 92 | 93 | --- 94 | -------------------------------------------------------------------------------- /docs/commands/log.md: -------------------------------------------------------------------------------- 1 | ![Wirecli Logo](/assets/img/favicon-16x16.png){.logo} **Log** 2 | 3 | --- 4 | 5 | ## List 6 | 7 | List all available log files. 8 | 9 | ```sh 10 | $ wirecli log:list 11 | ``` 12 | 13 | ### Examples 14 | 15 | List log files. 16 | 17 | ```sh 18 | $ wirecli log:list 19 | 20 | 6 logs 21 | ================ ================ ========= =========== 22 | Name Modified Entries Size 23 | ================ ================ ========= =========== 24 | errors 11 minutes ago 68 12 kB 25 | exceptions 36 minutes ago 23 3 kB 26 | messages 19 hours ago 6 552 bytes 27 | modules 19 hours ago 134 13 kB 28 | session 2 days ago 3 284 bytes 29 | system-updater 3 days ago 16 854 bytes 30 | ================ ================ ========= =========== 31 | ``` 32 | 33 | --- 34 | 35 | ## Tail 36 | 37 | List the most recent lines added to the log file (sorted newest to oldest). 38 | 39 | ```sh 40 | $ wirecli log:tail {selector} 41 | ``` 42 | 43 | ### Available options: 44 | 45 | ```sh 46 | --limit : Specify number of lines. Default: 10. (int) 47 | --text : Text to find. (string) 48 | --from : Oldest date to match entries. (int|string) * 49 | --to : Newest date to match entries. (int|string) * 50 | ``` 51 | 52 | \* You can use all integers and strings that are accepted by DateTime constructor. For example: 53 | 54 | - -2days 55 | - 2015-11-27 56 | - 1448617151 57 | 58 | ### Examples 59 | 60 | Get all log files as suggestion if you enter a non existing selector. 61 | 62 | ```sh 63 | $ wirecli log:tail error 64 | 65 | Log 'error' does not exist, choose one of `errors, exceptions, messages, modules, session, system-updater` 66 | ``` 67 | 68 | Output **messages** log, show 10 lines (default); 69 | 70 | ```sh 71 | $ wirecli log:tail messages 72 | 73 | Log messages 74 | ===================== ======= ================================================== ====================================== 75 | Date User URL Message 76 | ===================== ======= ================================================== ====================================== 77 | 2015-11-26 14:39:27 admin http://pw.dev/processwire/page/sort/ Updated sort for 2 pages 78 | 2015-11-26 14:39:16 admin http://pw.dev/processwire/page/sort/ Updated sort for 6 pages 79 | 2015-11-26 14:39:09 admin http://pw.dev/processwire/page/sort/ Updated sort for 8 pages 80 | 2015-11-26 13:51:17 admin http://pw.dev/processwire/page/sort/ Updated sort for 6 pages 81 | 2015-11-26 13:51:13 admin http://pw.dev/processwire/page/sort/ Updated sort for 6 pages 82 | 2015-11-26 13:49:07 admin http://pw.dev/processwire/setup/field/edit?id=44 Added tags to DB schema for 'images' 83 | ===================== ======= ================================================== ====================================== 84 | 85 | (6 in set, total: 6) 86 | ``` 87 | 88 | Output **modules** log, show 2 lines until yesterday. 89 | 90 | ```sh 91 | $ wirecli log:tail modules --limit=2 --to=-1days 92 | 93 | Log modules 94 | ===================== ======= ======================================= ====================================== 95 | Date User URL Message 96 | ===================== ======= ======================================= ====================================== 97 | 2015-11-25 16:29:46 admin http://pw.dev/processwire/module/ Failed to delete module 'Helloworld' 98 | 2015-11-24 15:19:24 admin http://pw.dev/processwire/xml-parser/ Saved module 'XmlParser' config data 99 | ===================== ======= ======================================= ====================================== 100 | 101 | (2 in set, total: 134) 102 | ``` 103 | 104 | Use `from` and `to` filters to reduce the list. 105 | 106 | ```sh 107 | $ wirecli log:tail system-updater --limit=2 --from=2015-11-20 --to=2015-11-25 108 | 109 | Log system-updater 110 | ===================== ====== ===== ================================= 111 | Date User URL Message 112 | ===================== ====== ===== ================================= 113 | 2015-11-24 14:44:38 Update #13: Completed! 114 | 2015-11-24 14:44:37 Update #13: Initializing update 115 | ===================== ====== ===== ================================= 116 | 117 | (2 in set, total: 16) 118 | ``` 119 | 120 | Find all **session** log entries which match "timed out". 121 | 122 | ```sh 123 | $ wirecli log:tail session --text="timed out" 124 | 125 | Log session 126 | ===================== ====== ===== ===================================================================================== 127 | Date User URL Message 128 | ===================== ====== ===== ===================================================================================== 129 | 2015-11-25 16:26:14 - ? User 'admin' - Session timed out (session older than 86400 seconds) (IP: 127.0.0.1) 130 | ===================== ====== ===== ===================================================================================== 131 | 132 | (1 in set, total: 3) 133 | ``` 134 | -------------------------------------------------------------------------------- /docs/commands/module.md: -------------------------------------------------------------------------------- 1 | ![Wirecli Logo](/assets/img/favicon-16x16.png){.logo} **Module** 2 | 3 | --- 4 | 5 | ## Download 6 | 7 | Downloads one or more ProcessWire modules. 8 | 9 | ```sh 10 | $ wirecli module:download {module-class},{module-class} 11 | ``` 12 | 13 | ### Available options: 14 | 15 | ```sh 16 | --github : Download module via github; use this option if the module is not added to the ProcessWire module directory yet 17 | --branch : Define specific branch to download from, if you use this option, --github is required 18 | ``` 19 | 20 | ### Examples 21 | 22 | Separate module names with commas to download two or more modules at once. 23 | 24 | ```sh 25 | $ wirecli module:download FlagPages,ImageExtra 26 | ``` 27 | 28 | Download module via github. 29 | 30 | ```sh 31 | $ wirecli module:download ContinentsAndCountries --github=justb3a/processwire-countries 32 | ``` 33 | 34 | If you want to download a branch, you have to provide the --github option, too. 35 | 36 | ```sh 37 | $ wirecli module:download FlagPages --github=marcus-herrmann/ProcessWire-FlagPages --branch=develop 38 | ``` 39 | 40 | --- 41 | 42 | ## Enable 43 | 44 | Enable and install a module. If the module is not found in the particular installation, wirecli downloads it first. 45 | 46 | ```sh 47 | $ wirecli module:enable {module-class},{module-class} 48 | ``` 49 | 50 | ### Available options: 51 | 52 | ```sh 53 | --github : Download module via github; use this option if the module is not added to the ProcessWire module directory yet 54 | --branch : Define specific branch to download from, if you use this option, --github is required 55 | ``` 56 | 57 | ### Examples 58 | 59 | Separate module names with commas to enable two or more modules at once. 60 | 61 | ```sh 62 | $ wirecli module:download FlagPages,ImageExtra 63 | ``` 64 | 65 | Download and enable module via github. 66 | 67 | ```sh 68 | $ wirecli module:download ContinentsAndCountries --github=justb3a/processwire-countries 69 | ``` 70 | 71 | Download `develop` branch and enable module. 72 | 73 | ```sh 74 | $ wirecli module:download FlagPages --github=marcus-herrmann/ProcessWire-FlagPages --branch=develop 75 | ``` 76 | 77 | --- 78 | 79 | ## Disable 80 | 81 | Disables and uninstalls one or more modules. 82 | 83 | ```sh 84 | $ wirecli module:disable {class-name} {class-name} 85 | ``` 86 | 87 | ### Available options: 88 | 89 | ```sh 90 | --rm : Remove module files 91 | ``` 92 | 93 | ### Examples 94 | 95 | Deinstall modules but keep files. 96 | 97 | ```sh 98 | $ wirecli module:disable FlagPages ImageExtra 99 | ``` 100 | 101 | Deinstall modules and remove files. 102 | 103 | ```sh 104 | $ wirecli module:disable --rm FlagPages ImageExtra 105 | ``` 106 | 107 | --- 108 | 109 | ## Upgrade 110 | 111 | Upgrades given module(s). 112 | 113 | ```sh 114 | $ wirecli module:upgrade {class-name} {class-name}* 115 | ``` 116 | 117 | \* This argument is optional. If you want to check for module updates, just skip it. 118 | 119 | ### Available options: 120 | 121 | ```sh 122 | --check : Just check for module upgrades. 123 | ``` 124 | 125 | ### Examples 126 | 127 | Check if module upgrades are available. 128 | 129 | ```sh 130 | $ wirecli module:upgrade 131 | 132 | An upgrade is available for: 133 | - FlagPages: 0.0.8 -> 0.2.3 134 | - ImageExtra: 0.0.1 -> 0.0.3 135 | ``` 136 | 137 | Download and upgrade existing module `ImageExtra`. 138 | 139 | ```sh 140 | $ wirecli module:upgrade ImageExtra 141 | 142 | An upgrade for ImageExtra is available: 0.0.3 143 | Downloading module ImageExtra... 144 | 840.40 KB/840.40 KB ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 100% 145 | 146 | Preparing module... 147 | 148 | Module ImageExtra downloaded successfully. 149 | 150 | Module `ImageExtra` was updated successfully. 151 | ``` 152 | 153 | --- 154 | 155 | ## Generate 156 | 157 | Generates a module via [modules.pw](http://modules.pw/). 158 | 159 | ```sh 160 | $ wirecli module:generate {class-name} 161 | ``` 162 | 163 | ### Available options: 164 | 165 | ```sh 166 | --title : Module title 167 | --mod-version : Module version 168 | --author : Module author 169 | --link : Module link 170 | --summary : Module summary 171 | --type : Module type 172 | --extends : Module extends 173 | --implements : Module implements (Interface) 174 | --require-pw : ProcessWire version compatibility 175 | --require-php : PHP version compatibility 176 | --is-autoload : autoload = true 177 | --is-singular : singular = true 178 | --is-permanent : permanent = true 179 | --with-external-json : external json config file 180 | --with-copyright : Adds copyright in comments 181 | --with-uninstall : Adds uninstall method 182 | --with-sample-code : Adds sample code 183 | --with-config-page : Adds config page 184 | ``` 185 | 186 | ### Examples 187 | 188 | For more information on values and module authoring, visit the great generator [modules.pw](http://modules.pw/). 189 | 190 | --- 191 | -------------------------------------------------------------------------------- /docs/commands/page.md: -------------------------------------------------------------------------------- 1 | ![Wirecli Logo](/assets/img/favicon-16x16.png){.logo} **Page** 2 | 3 | --- 4 | 5 | ## List 6 | 7 | Output the page structure of the current ProcessWire installation with hierarchy, titles, IDs and page names. 8 | 9 | ```sh 10 | $ wirecli page:list 11 | ``` 12 | 13 | ### Available options: 14 | 15 | ```sh 16 | --all : Get a list of all pages (recursive) without admin-pages 17 | --trash : Get a list of trashed pages (recursive) without admin-pages 18 | --level : How many levels to show 19 | --start : start page id 20 | ``` 21 | ### Examples 22 | 23 | List all pages. 24 | 25 | ```sh 26 | $ wirecli page:list 27 | 28 | |-- Home { 1, home } 29 | | |-- About { 1001, basic-page } 30 | | |-- Child page example 1 { 1002, basic-page } 31 | | |-- Child page example 3 { 1014, basic-page } 32 | | |-- Site Map { 1005, sitemap } 33 | ``` 34 | 35 | Get a list of all (including hidden) pages (recursive) without admin-pages. 36 | 37 | ```sh 38 | $ wirecli page:list --all 39 | 40 | |-- Home { 1, home } 41 | | |-- About { 1001, basic-page } 42 | | |-- Child page example 1 { 1002, basic-page } 43 | | |-- Child page example 3 { 1014, basic-page } 44 | | |-- Site Map { 1005, sitemap } 45 | | |-- Search { 1000, search } 46 | | |-- 404 Page { 27, basic-page } 47 | ``` 48 | 49 | Get a list of trashed pages (recursive) without admin-pages. 50 | 51 | ```sh 52 | $ wirecli page:list --trash 53 | 54 | |-- Trash { 7, admin } 55 | | |-- Child page example 2 { 1004, basic-page } 56 | ``` 57 | 58 | Get a list of pages output 1 level. 59 | 60 | ```sh 61 | $ wirecli page:list --level=1 62 | 63 | |-- Home { 1, home } 64 | | |-- About { 1001, basic-page } 65 | | |-- Site Map { 1005, sitemap } 66 | ``` 67 | 68 | Get a list of pages, starting by the page with id 1001. 69 | 70 | ```sh 71 | $ wirecli page:list --start=1001 72 | 73 | |-- About { 1001, basic-page } 74 | | |-- Child page example 1 { 1002, basic-page } 75 | | |-- Child page example 3 { 1014, basic-page } 76 | ``` 77 | 78 | Get a list of all pages inluding trashed pages ouput 1 level. 79 | 80 | ```sh 81 | $ wirecli page:list --all --trash --level=1 82 | 83 | |-- Home { 1, home } 84 | | |-- About { 1001, basic-page } 85 | | |-- Site Map { 1005, sitemap } 86 | | |-- Search { 1000, search } 87 | | |-- 404 Page { 27, basic-page } 88 | | |-- Trash { 7, admin } 89 | ``` 90 | 91 | --- 92 | 93 | ## Create 94 | 95 | Create a new page with the given parameters. 96 | 97 | ```sh 98 | $ wirecli page:create 99 | ``` 100 | 101 | ### Available options: 102 | 103 | ```sh 104 | --template : template for new page 105 | --parent : parent page name 106 | --title : page title 107 | --file : field data file (json) 108 | ``` 109 | 110 | ### Examples 111 | 112 | Create a new page. 113 | 114 | ```sh 115 | $ wirecli page:create example --template=basic-page --parent=home --title="Example Page" 116 | ``` 117 | 118 | Create multiple pages. 119 | 120 | ```sh 121 | $ wirecli page:create example-1,example-2,example-3 --template=basic-page --parent=home 122 | ``` 123 | 124 | Create new page, ask for template. 125 | 126 | ```sh 127 | $ wirecli page:create newpage --title="Child page example 3" 128 | 129 | Please enter the template : basic-page 130 | ``` 131 | 132 | Create a new page and import field data from valid json file. 133 | 134 | ```sh 135 | $ wirecli page:create example --template=basic-page --parent=home --title="Example Page" --file=import.json 136 | ``` 137 | 138 | --- 139 | 140 | ## Delete 141 | 142 | Put a page into the trash. Selector is either page name, page id or selector. 143 | 144 | ```sh 145 | $ wirecli page:delete {selector} 146 | ``` 147 | 148 | ### Available options: 149 | 150 | ```sh 151 | --rm : force delete the selected page without putting it in the trash first 152 | ``` 153 | 154 | ### Examples 155 | 156 | Delete all pages where the parent id equals 1004: 157 | 158 | ```sh 159 | $ wirecli page:delete --rm "has_parent=1004" 160 | ``` 161 | 162 | Delete page with id 1005: 163 | 164 | ```sh 165 | $ wirecli page:delete 1005 166 | ``` 167 | 168 | Delete pages with id 1002 and 1003: 169 | 170 | ```sh 171 | $ wirecli page:delete 1002,1003 172 | ``` 173 | 174 | Delete pages with page name *About*: 175 | 176 | ```sh 177 | $ wirecli page:delete About 178 | ``` 179 | 180 | --- 181 | 182 | ## Empty Trash 183 | 184 | Empty ProcessWire's trash. 185 | 186 | ```sh 187 | $ wirecli page:emptytrash 188 | ``` 189 | 190 | --- 191 | -------------------------------------------------------------------------------- /docs/commands/role.md: -------------------------------------------------------------------------------- 1 | ![Wirecli Logo](/assets/img/favicon-16x16.png){.logo} **Role** 2 | 3 | --- 4 | 5 | ## List 6 | 7 | List available roles. 8 | 9 | ```sh 10 | $ wirecli role:list 11 | ``` 12 | 13 | ### Examples 14 | 15 | List all roles. 16 | 17 | ```sh 18 | $ wirecli role:list 19 | 20 | - editor 21 | - guest 22 | - newsletter 23 | - superuser 24 | ``` 25 | 26 | --- 27 | 28 | ## Create 29 | 30 | Create new role(s) with the given parameters. 31 | 32 | ```sh 33 | $ wirecli role:create {role-name,role-name} 34 | ``` 35 | 36 | ### Examples 37 | 38 | Create a new role. 39 | 40 | ```sh 41 | $ wirecli role:create editor 42 | 43 | Role 'editor' created successfully! 44 | ``` 45 | 46 | --- 47 | 48 | ## Delete 49 | 50 | Delete role(s) with the given parameters. 51 | 52 | ```sh 53 | $ wirecli role:delete {role-name,role-name} 54 | ``` 55 | 56 | ### Examples 57 | 58 | Delete a role. 59 | 60 | ```sh 61 | $ wirecli role:delete editor 62 | 63 | Role 'editor' deleted successfully! 64 | ``` 65 | 66 | --- 67 | -------------------------------------------------------------------------------- /docs/commands/template.md: -------------------------------------------------------------------------------- 1 | ![Wirecli Logo](/assets/img/favicon-16x16.png){.logo} **Template** 2 | 3 | --- 4 | 5 | ## List 6 | 7 | List all ProcessWire templates. 8 | 9 | ```sh 10 | $ wirecli template:list 11 | ``` 12 | 13 | ### Available options: 14 | 15 | ```sh 16 | --advanced : Show system templates as well; by default, system/internal templates are not shown 17 | ``` 18 | 19 | ### Examples 20 | 21 | List all available ProcessWire templates. 22 | 23 | ```sh 24 | $ wirecli templates:list 25 | 26 | ============ ======== ======= ============= ======== 27 | Template Fields Pages Modified Access 28 | ============ ======== ======= ============= ======== 29 | basic-page 6 1 3 weeks ago 30 | home 6 1 3 weeks ago 31 | ============ ======== ======= ============= ======== 32 | ``` 33 | 34 | List all available ProcessWire templates including system/internal templates. 35 | 36 | ```sh 37 | $ wirecli templates:list --advanced 38 | 39 | ============ ======== ======= ============= ======== 40 | Template Fields Pages Modified Access 41 | ============ ======== ======= ============= ======== 42 | admin 2 26 3 weeks ago ✖ 43 | basic-page 6 1 3 weeks ago 44 | home 6 1 3 weeks ago 45 | permission 1 15 Never ✖ 46 | role 1 4 Never ✖ 47 | user 3 7 Never ✖ 48 | ============ ======== ======= ============= ======== 49 | ``` 50 | 51 | --- 52 | 53 | ## Create 54 | 55 | Create a ProcessWire template. 56 | 57 | ```sh 58 | $ wirecli template:create {name} 59 | ``` 60 | 61 | ### Available options: 62 | 63 | ```sh 64 | --fields : Attach existing fields to template, comma separated 65 | --nofile : Prevent template file creation 66 | ``` 67 | 68 | ### Examples 69 | 70 | Create a template with given name. 71 | 72 | ```sh 73 | $ wirecli template:create new-template 74 | ``` 75 | 76 | Create a template with given name but prevent template file creation. 77 | 78 | ```sh 79 | $ wirecli template:create new-template --nofile 80 | ``` 81 | 82 | Create a template with given name and assign some fields. 83 | 84 | ```sh 85 | $ wirecli template:create new-template --fields=images,headline,body 86 | ``` 87 | 88 | --- 89 | 90 | ## Delete 91 | 92 | Delete one or more ProcessWire templates. 93 | 94 | ```sh 95 | $ wirecli template:delete {template},{template} 96 | ``` 97 | 98 | ### Available options: 99 | 100 | ```sh 101 | --nofile : Prevent template file deletion 102 | ``` 103 | 104 | ### Examples 105 | 106 | Delete template and belonging file. 107 | 108 | ```sh 109 | $ wirecli template:delete basic-page 110 | ``` 111 | 112 | Delete template but keep belonging file. 113 | 114 | ```sh 115 | $ wirecli template:delete basic-page --nofile 116 | ``` 117 | 118 | --- 119 | 120 | ## Fields 121 | 122 | Assign given fields to a given template. 123 | 124 | ```sh 125 | $ wirecli template:fields {template} 126 | ``` 127 | 128 | ### Available options: 129 | 130 | ```sh 131 | --fields : Attach existing fields to template, comma separated 132 | ``` 133 | 134 | ### Examples 135 | 136 | Assign existing fields to template. 137 | 138 | ```sh 139 | $ wirecli template:fields basic-page --fields=images,headline,body 140 | ``` 141 | 142 | --- 143 | 144 | ## Tag 145 | 146 | Tag one or more existing templates. 147 | 148 | ```sh 149 | $ wirecli template:tag {template} --tag={tag} 150 | ``` 151 | 152 | ### Available options: 153 | 154 | ```sh 155 | --tag : tag name 156 | ``` 157 | 158 | ### Examples 159 | 160 | Tag template `basic-page` and `home` with tag `general`. 161 | 162 | ```sh 163 | $ wirecli template:tag basic-page,home --tag=general 164 | ``` 165 | 166 | --- 167 | 168 | ## Info 169 | 170 | Displays detailed information about a specific template. 171 | 172 | ```sh 173 | $ wirecli template:info {template} 174 | ``` 175 | 176 | ### Examples 177 | 178 | Get detailed information about template `basic-page`. 179 | 180 | ```sh 181 | $ wirecli template:info basic-page 182 | ``` 183 | 184 | --- 185 | -------------------------------------------------------------------------------- /docs/commands/user.md: -------------------------------------------------------------------------------- 1 | ![Wirecli Logo](/assets/img/favicon-16x16.png){.logo} **User** 2 | 3 | --- 4 | 5 | ## List 6 | 7 | List all users. 8 | 9 | ```sh 10 | $ wirecli user:list 11 | ``` 12 | 13 | ### Available options: 14 | 15 | ```sh 16 | --role : filter by user role (given the role exists) 17 | ``` 18 | 19 | ### Examples 20 | 21 | List all users. 22 | 23 | ```sh 24 | $ wirecli user:list 25 | 26 | Users: 2 27 | 28 | ========== =========== =========== ================== 29 | Username E-Mail Superuser Roles 30 | ========== =========== =========== ================== 31 | admin pw@ws.com ✔ guest, superuser 32 | guest guest 33 | ========== =========== =========== ================== 34 | ``` 35 | 36 | List all superusers. 37 | 38 | ```sh 39 | $ wirecli user:list --role=superuser 40 | 41 | Users: 1 42 | 43 | ========== =========== =========== ================== 44 | Username E-Mail Superuser Roles 45 | ========== =========== =========== ================== 46 | admin pw@ws.com ✔ guest, superuser 47 | ========== =========== =========== ================== 48 | ``` 49 | 50 | --- 51 | 52 | ## Create 53 | 54 | Create an user. 55 | 56 | ```sh 57 | $ wirecli user:create {user-name} 58 | ``` 59 | 60 | ### Available options: 61 | 62 | ```sh 63 | --email : mail address for the user 64 | --password : password for the user 65 | --roles : assign user roles, comma separated (given the role exist), role `guest` is attached by default 66 | ``` 67 | 68 | ### Examples 69 | 70 | Create a new user by given email, password and role. 71 | 72 | ```sh 73 | $ wirecli user:create editor --email="editor@ws.pw" --password=cgBG+T9e7Nu2 --roles=editor 74 | ``` 75 | 76 | Create a new user with role guest. 77 | 78 | ```sh 79 | $ wirecli user:create pwguest --email="guest@ws.pw" --password=ws6jem6un3V& 80 | ``` 81 | 82 | Create a new user with roles superuser and editor. 83 | 84 | ```sh 85 | $ wirecli user:create pwadmin --roles=superuser,editor 86 | 87 | Please enter a email address : pwadmin@ws.pw 88 | Please enter a password : 89 | ``` 90 | 91 | --- 92 | 93 | ## Delete 94 | 95 | Delete an user or multiple users at once (by name or role). 96 | 97 | ```sh 98 | $ wirecli user:delete {user-name},{user-name}* 99 | ``` 100 | 101 | \* This argument is optional. If you want to delete users by role instead, just skip it. 102 | 103 | ### Available options: 104 | 105 | ```sh 106 | --role : role name 107 | ``` 108 | 109 | ### Examples 110 | 111 | Delete an user. 112 | 113 | ```sh 114 | $ wirecli user:delete pweditor 115 | ``` 116 | 117 | Delete multiple users. 118 | 119 | ```sh 120 | $ wirecli user:delete pwadmin,pwguest 121 | ``` 122 | 123 | Delete users by given role. 124 | 125 | ```sh 126 | $ wirecli user:delete --role=editor 127 | ``` 128 | 129 | --- 130 | 131 | ## Update 132 | 133 | Update an existing user. 134 | 135 | ```sh 136 | $ wirecli user:update {user-name} 137 | ``` 138 | 139 | ### Available options: 140 | 141 | ```sh 142 | --email : mail address for the user 143 | --password : password for the user 144 | --roles : assign user roles, comma separated (given the role exist), role `guest` is attached by default 145 | ``` 146 | 147 | ### Examples 148 | 149 | Update an user; sets new email address. 150 | 151 | ```sh 152 | $ wirecli user:update pweditor --email=otto@example.org 153 | ``` 154 | 155 | Update an user; sets new email, password and roles. 156 | 157 | ```sh 158 | $ wirecli user:update pwguest --email=otto@example.org --roles=superuser,editor --password=somepass 159 | ``` 160 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wire-cli", 3 | "version": "1.4.12", 4 | "description": "An extendable ProcessWire command line interface", 5 | "main": "commitlint.config.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "scripts": { 10 | "test": "phpunit", 11 | "commit": "commit", 12 | "release": "release-it --no-npm.publish" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/wirecli/wire-cli.git" 17 | }, 18 | "keywords": [ 19 | "processwire", 20 | "cli", 21 | "shell" 22 | ], 23 | "author": "Joani Eizmendi (flydev-fr)", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/wirecli/wire-cli/issues" 27 | }, 28 | "homepage": "https://github.com/wirecli/wire-cli#readme", 29 | "devDependencies": { 30 | "@commitlint/cli": "^17.6.6", 31 | "@commitlint/config-conventional": "^17.6.6", 32 | "@commitlint/prompt-cli": "^17.6.6", 33 | "@release-it/bumper": "^5.0.0", 34 | "@release-it/conventional-changelog": "^7.0.0", 35 | "release-it": "^16.1.0" 36 | }, 37 | "release-it": { 38 | "github": { 39 | "release": true 40 | }, 41 | "git": { 42 | "requireBranch": "main", 43 | "requireCleanWorkingDir": false 44 | }, 45 | "hooks": { 46 | "before:init": [ 47 | "git pull" 48 | ], 49 | "after:bump": [ 50 | "npx auto-changelog -p" 51 | ] 52 | }, 53 | "plugins": { 54 | "@release-it/bumper": { 55 | "out": [ 56 | "composer.json", 57 | "./app/version.json" 58 | ] 59 | }, 60 | "@release-it/conventional-changelog": { 61 | "infile": "CHANGELOG.md", 62 | "header": "# Changelog", 63 | "preset": { 64 | "name": "conventionalcommits", 65 | "types": [ 66 | { 67 | "type": "feat", 68 | "section": "🚀 Features" 69 | }, 70 | { 71 | "type": "fix", 72 | "section": "🐞 Bug Fixes" 73 | } 74 | ] 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Tests 26 | 27 | 28 | 29 | 30 | 31 | ./ 32 | 33 | ./Tests 34 | ./vendor 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/Commands/Backup/BackupDatabaseCommand.php: -------------------------------------------------------------------------------- 1 | setName('backup:db') 29 | ->setDescription('Performs database dump') 30 | ->addOption('filename', null, InputOption::VALUE_REQUIRED, 'Provide a file name for the dump') 31 | ->addOption('target', null, InputOption::VALUE_REQUIRED, 'Provide a file path for the dump (relative to ProcessWire root directory or absolute)'); 32 | } 33 | 34 | /** 35 | * @param InputInterface $input 36 | * @param OutputInterface $output 37 | * @return int|null|void 38 | */ 39 | protected function execute(InputInterface $input, OutputInterface $output) { 40 | $this->init($input, $output); 41 | $tools = new Tools($output); 42 | $tools->writeBlockCommand($this->getName()); 43 | 44 | $config = \ProcessWire\wire('config'); 45 | $database = $config->dbName; 46 | $host = $config->dbHost; 47 | $user = $config->dbUser; 48 | $pass = $config->dbPass; 49 | $port = $config->dbPort; 50 | 51 | $inFilename = $input->getOption('filename'); 52 | $filename = $inFilename ? "{$inFilename}.sql" : 'dump-' . date("Y-m-d-H-i-s") . '.sql'; 53 | $target = $input->getOption('target') ? $input->getOption('target') : ''; 54 | if ($target && !preg_match('/$\//', $target)) $target = "$target/"; 55 | 56 | try { 57 | $dump = new Dump; 58 | $dump 59 | ->file($target . $filename) 60 | ->dsn("mysql:dbname={$database};host={$host};port={$port}") 61 | ->user($user) 62 | ->pass($pass) 63 | ->tmp(getcwd() . 'site/assets/tmp'); 64 | 65 | new Export($dump); 66 | } catch (Exception $e) { 67 | $tools->writeBlockError(array( 68 | "Export failed with message: `{$e->getMessage()}`.", 69 | 'Please make sure that the provided target exists.' 70 | )); 71 | exit(1); 72 | } 73 | 74 | $tools->writeSuccess("Dumped database into `{$target}{$filename}` successfully."); 75 | 76 | return static::SUCCESS; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Commands/Backup/BackupDuplicatorCommand.php: -------------------------------------------------------------------------------- 1 | setName('backup:duplicator') 35 | ->setDescription('Performs Duplicator operation on packages') 36 | ->addArgument('list', InputArgument::OPTIONAL, 'List packages on local filesystem') 37 | ->addArgument('new', InputArgument::OPTIONAL, 'Backup database and file as zip package'); 38 | } 39 | 40 | /** 41 | * List of packages 42 | * 43 | * @param InputInterface $input 44 | * @param OutputInterface $output 45 | * @return int|null|void 46 | */ 47 | protected function execute(InputInterface $input, OutputInterface $output) { 48 | $this->init($input, $output); 49 | $cmdName = $this->getName(); 50 | 51 | $return = null; 52 | 53 | $arg = $input->getArgument('list'); 54 | if ($arg === 'list') { 55 | $cmdName = sprintf('%s:%s', $cmdName, 'list'); 56 | $packageList = new DuplicatorListCommand(); 57 | $return = $packageList->list($input, $output, $cmdName); 58 | } 59 | elseif ($input->getArgument('new')) { 60 | $cmdName = sprintf('%s:%s', $cmdName, 'new'); 61 | $packageNew = new DuplicatorNewPackageCommand(); 62 | $return = $packageNew->new($input, $output, $cmdName); 63 | } 64 | else { 65 | $output->writeln('No argument given'); 66 | return static::FAILURE; 67 | } 68 | 69 | return $return; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Commands/Backup/BackupImagesCommand.php: -------------------------------------------------------------------------------- 1 | setName('backup:images') 25 | ->setDescription('Performs images backup') 26 | ->addOption('selector', null, InputOption::VALUE_REQUIRED, 'Provide a pages selector') 27 | ->addOption('field', null, InputOption::VALUE_REQUIRED, 'Provide a image field name') 28 | ->addOption('target', null, InputOption::VALUE_REQUIRED, 'Provide a destination folder'); 29 | } 30 | 31 | /** 32 | * @param InputInterface $input 33 | * @param OutputInterface $output 34 | * @return int|null|void 35 | */ 36 | protected function execute(InputInterface $input, OutputInterface $output) { 37 | $this->init($input, $output); 38 | $tools = new Tools($output); 39 | $tools->writeBlockCommand($this->getName()); 40 | 41 | if ($input->getOption('target')) { 42 | $path = \ProcessWire\wire('config')->paths->root . $input->getOption('target'); 43 | } else { 44 | $path = \ProcessWire\wire('config')->paths->root . 'dump-' . date('Y-m-d-H-i-s'); 45 | } 46 | 47 | if (!file_exists($path)) mkdir($path); 48 | 49 | $pages = \ProcessWire\wire('pages'); 50 | if ($input->getOption('selector')) { 51 | $pages = \ProcessWire\wire('pages')->find($input->getOption('selector')); 52 | } else { 53 | $pages = \ProcessWire\wire('pages')->find("has_parent!=2,id!=2|7,status<" . Page::statusTrash . ",include=all"); 54 | } 55 | 56 | $fieldname = ($input->getOption('field')) ? $input->getOption('field') : 'images'; 57 | 58 | if ($pages) { 59 | $total = 0; 60 | $imgNames = array(); 61 | foreach ($pages as $page) { 62 | if ($page->$fieldname) { 63 | foreach ($page->$fieldname as $img) { 64 | if (!in_array($img->name, $imgNames)) { 65 | if (function_exists('copy')) { 66 | // php 5.5+ 67 | copy($img->filename, $path . '/' . $img->name); 68 | } else { 69 | $content = file_get_contents($img->filename); 70 | $fp = fopen($path, "w"); 71 | fwrite($fp, $content); 72 | fclose($fp); 73 | } 74 | $total++; 75 | $imgNames[] = $img->name; 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | if ($total > 0) { 83 | $tools->writeSuccess("Dumped {$total} images into `{$path}` successfully."); 84 | } else { 85 | $tools->writeError("No images found. Recheck your options."); 86 | } 87 | 88 | return static::SUCCESS; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/Commands/Backup/Duplicator/DuplicatorListCommand.php: -------------------------------------------------------------------------------- 1 | init($input, $output); 31 | $tools = new Tools($output); 32 | $tools->writeBlockCommand($name); 33 | 34 | try { 35 | $duplicator_mod = \ProcessWire\wire('modules')->get('Duplicator'); 36 | $packages = \ProcessWire\Dup_Util::getPackages(\ProcessWire\wire('config')->paths->assets . 'backups', 'zip'); 37 | $return = array(); 38 | foreach ($packages as $pkg) { 39 | if (strrchr($pkg, '.') != strrchr($duplicator_mod::DUP_PACKAGE_EXTENSION, '.')) continue; 40 | $packagePath = $duplicator_mod->path .DIRECTORY_SEPARATOR. $pkg; 41 | $return[] = array( 42 | 'name' => $pkg, 43 | 'path' => $packagePath, 44 | 'size' => \ProcessWire\DUP_Util::human_filesize(\ProcessWire\DUP_Util::filesize($packagePath)), 45 | 'date' => date('Y-m-d H:i:s', filemtime($packagePath)), 46 | 'type' => 'local' 47 | ); 48 | } 49 | 50 | if (count($return) == 0) { 51 | $tools->writeBlockBasic("No packages found"); 52 | return Command::SUCCESS; 53 | } 54 | $data = []; 55 | foreach ($return as $package) { 56 | $data[] = array( 57 | $package['name'], 58 | \ProcessWire\wireRelativeTimeStr($package['date']), 59 | \ProcessWire\Dup_Util::filesize($package['path']), 60 | $package['type'] 61 | ); 62 | } 63 | $headers = array('Name', 'Modified', 'Size', 'Type'); 64 | $tables = new Tables($output); 65 | $logTables = array($tables->buildTable($data, $headers)); 66 | $tables->renderTables($logTables, false); 67 | $count = count($data); 68 | $tools->writeCount($count); 69 | } catch (Exception $e) { 70 | $tools->writeBlockError($e->getMessage()); 71 | return Command::FAILURE; 72 | } 73 | 74 | return Command::SUCCESS; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Commands/Backup/Duplicator/DuplicatorNewPackageCommand.php: -------------------------------------------------------------------------------- 1 | writeBlockCommand($name); 39 | 40 | //$tools->writeInfo('Creating package...'); 41 | 42 | try { 43 | $package = $this->buildPackage($output); 44 | if ($package) { 45 | // check package error 46 | if (count($package['info']['pw']['errors'])) { 47 | foreach ($package['info']['pw']['errors'] as $error) { 48 | $tools->writeError($error); 49 | } 50 | return Command::FAILURE; 51 | } 52 | 53 | // return success 54 | $name = basename($package['info']['pw']['zipfile']); 55 | $size = \ProcessWire\DUP_Util::human_filesize(\ProcessWire\DUP_Util::filesize($package['info']['pw']['zipfile'])); 56 | $data = array( 57 | array( 58 | $name, 59 | $size, 60 | 'local' 61 | ) 62 | ); 63 | $tools->nl(3); 64 | $headers = array('Name', 'Size', 'Type'); 65 | $tables = new Tables($output); 66 | $logTables = array($tables->buildTable($data, $headers)); 67 | $tables->renderTables($logTables, false); 68 | $tools->writeSuccess('Package created'); 69 | $tools->nl(); 70 | } else { 71 | $tools->writeError('Package creation failed'); 72 | return Command::FAILURE; 73 | } 74 | } catch (Exception $e) { 75 | $tools->writeBlockError($e->getMessage()); 76 | return Command::FAILURE; 77 | } 78 | 79 | return Command::SUCCESS; 80 | } 81 | 82 | /* 83 | * build the package 84 | * return a ZIP file containing the database and ProcessWire stucture zipped files 85 | * TODO: better management for future use in deployment 86 | */ 87 | protected function buildPackage(OutputInterface $output) 88 | { 89 | $this->dupmod = \ProcessWire\wire('modules')->get('Duplicator'); 90 | 91 | // ProgressBar::setPlaceholderFormatterDefinition('current', function (ProgressBar $bar) { 92 | // return str_pad($bar->getProgress(), 2, ' ', STR_PAD_LEFT); 93 | // }); 94 | // ProgressBar::setPlaceholderFormatterDefinition('max', function (ProgressBar $bar) { 95 | // return $bar->getMaxSteps(); 96 | // }); 97 | 98 | 99 | $tools = new Tools($output); 100 | $tools->nl(); 101 | 102 | // creates a new progress bar (50 units) 103 | $progressBar = new ProgressBar($output, 3); 104 | $progressBar->setBarCharacter('⚬'); 105 | $progressBar->setEmptyBarCharacter("⚬"); 106 | $progressBar->setProgressCharacter("➤"); 107 | $progressBar->setFormat( 108 | "%status%\n%current%/%max% [%bar%] %percent:3s%%\n %estimated:-6s% %memory:6s%" 109 | ); 110 | $progressBar->setRedrawFrequency(max(1, floor(3 / 1000))); 111 | $progressBar->setBarWidth(60); 112 | $progressBar->setMessage("Starting...", 'status'); 113 | $progressBar->start(); 114 | 115 | \ProcessWire\DUP_Util::timer('build'); 116 | 117 | $defaultOptions = array( 118 | 'filename' => \ProcessWire\DUP_Util::formatFilename(str_replace('-', '_', $this->dupmod->packageName), 'package.zip'), 119 | 'path' => $this->dupmod->path, 120 | ); 121 | $options = $defaultOptions; 122 | $packageInfos = array(); 123 | 124 | try { 125 | $dbbackup = $this->dupmod->buildDatabaseBackup(); 126 | if ($dbbackup == false) { 127 | \ProcessWire\DUP_Logs::log("- an error occured during database backup."); 128 | return false; 129 | } 130 | $packageInfos['db'] = $dbbackup; 131 | $progressBar->advance(0); 132 | $progressBar->setMessage("Database backup done...", 'status'); 133 | 134 | $pwbackup = $this->dupmod->buildProcessWireBackup(); 135 | if ($pwbackup == false) { 136 | \ProcessWire\DUP_Logs::log("- an error occured during package build."); 137 | return false; 138 | } 139 | $packageInfos['pw'] = $pwbackup; 140 | $progressBar->advance(1); 141 | $progressBar->setMessage("Directory backup done...", 'status'); 142 | 143 | 144 | if (true) //ATO: Add SQL ZIP to archive 145 | { 146 | $zip = new \ZipArchive(); 147 | if ($zip->open($pwbackup['zipfile']) !== true) { 148 | throw new \ProcessWire\WireException("Unable to open ZIP: {$pwbackup['zipfile']}"); 149 | } 150 | 151 | $zip->addFile($dbbackup['zipfile'], basename($dbbackup['zipfile'])); 152 | $zip->close(); 153 | $zipfile = $pwbackup['zipfile']; //ATO: Fix to properly return new archive 154 | \ProcessWire\DUP_Util::deleteFile($dbbackup['zipfile']); 155 | } else { 156 | $files = array( 157 | $packageInfos['db']['zipfile'], 158 | $packageInfos['pw']['zipfile'], 159 | ); 160 | $zipfile = $options['path'] . DS . $options['filename']; 161 | $result = \ProcessWire\wireZipFile($zipfile, $files); 162 | foreach ($files as $file) { 163 | \ProcessWire\DUP_Util::deleteFile($file); 164 | } 165 | 166 | foreach ($result['errors'] as $error) { 167 | \ProcessWire\DUP_Logs::log("ZIP add failed: $error"); 168 | } 169 | } 170 | 171 | if (file_exists($zipfile)) { 172 | $package['zipfile'] = $zipfile; 173 | $package['size'] = filesize($zipfile); 174 | $package['info'] = $packageInfos; 175 | //$fp = fopen($options['path'] . DS . $options['filename'] . '.json', 'w'); 176 | //fwrite($fp, json_encode($package)); 177 | //fclose($fp); 178 | \ProcessWire\DUP_Logs::log("- package built successfully in " . \ProcessWire\DUP_Util::timer('build') . "sec"); 179 | 180 | 181 | $progressBar->advance(2); 182 | $progressBar->setMessage("Package built", 'status'); 183 | 184 | return $package; 185 | } else { 186 | \ProcessWire\DUP_Logs::log("- package build failed, {$zipfile} doesn't exist"); 187 | return false; 188 | } 189 | 190 | $progressBar->finish(); 191 | 192 | } catch (\Exception $ex) { 193 | \ProcessWire\DUP_Logs::log($ex->getMessage()); 194 | } 195 | 196 | return false; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Commands/Backup/RestoreDatabaseCommand.php: -------------------------------------------------------------------------------- 1 | setName('backup:restore') 29 | ->setDescription('Performs database restoring') 30 | ->addOption('filename', null, InputOption::VALUE_REQUIRED, 'Provide a file name to restore from'); 31 | } 32 | 33 | /** 34 | * @param InputInterface $input 35 | * @param OutputInterface $output 36 | * @return int|null|void 37 | */ 38 | protected function execute(InputInterface $input, OutputInterface $output) { 39 | $this->init($input, $output); 40 | $tools = new Tools($output); 41 | $tools->writeBlockCommand($this->getName()); 42 | 43 | $config = \ProcessWire\wire('config'); 44 | $database = $config->dbName; 45 | $host = $config->dbHost; 46 | $user = $config->dbUser; 47 | $pass = $config->dbPass; 48 | 49 | $inFilename = $input->getOption('filename'); 50 | $filename = $inFilename ? "{$inFilename}.sql" : 'dump-' . date("Y-m-d-H-i-s") . '.sql'; 51 | $target = $input->getOption('target') ? $input->getOption('target') : ''; 52 | if ($target && !preg_match('/$\//', $target)) $target = "$target/"; 53 | 54 | try { 55 | $dump = new Dump; 56 | $dump 57 | ->file($target . $filename) 58 | ->dsn("mysql:dbname={$database};host={$host}") 59 | ->user($user) 60 | ->pass($pass) 61 | ->tmp(getcwd() . 'site/assets/tmp'); 62 | 63 | new Export($dump); 64 | } catch (Exception $e) { 65 | $tools->writeBlockError(array( 66 | "Export failed with message: `{$e->getMessage()}`.", 67 | 'Please make sure that the provided target exists.' 68 | )); 69 | exit(1); 70 | } 71 | 72 | $tools->writeSuccess("Dumped database into `{$target}{$filename}` successfully."); 73 | 74 | return static::SUCCESS; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Commands/Common/CheatCommand.php: -------------------------------------------------------------------------------- 1 | setName('cheat') 32 | ->setDescription('Displays styles.'); 33 | } 34 | 35 | /** 36 | * @param InputInterface $input 37 | * @param OutputInterface $output 38 | * @return int|null|void 39 | */ 40 | protected function execute(InputInterface $input, OutputInterface $output) { 41 | $this->init($input, $output); 42 | $tools = new Tools($output); 43 | $tools->writeBlockCommand($this->getName()); 44 | 45 | $tools->writeBlockBasic(array( 46 | ' use Wirecli\Helpers\WsTools as Tools;', 47 | ' $tools = new Tools($output);', 48 | ' $tools->writeInfo(\'This is how it should be used!\')' 49 | )); 50 | 51 | $tools->writeSuccess('Write Success!'); 52 | $tools->writeInfo('Write Info!'); 53 | $tools->writeComment('Write Comment!'); 54 | $tools->writeLink('Write Link!'); 55 | $tools->writeBlock('Write Block!'); 56 | $tools->writeError('Write Error!'); 57 | $tools->writeHeader('Write Header!'); 58 | $tools->writeMark('Write Mark!'); 59 | $tools->writeSection('Write Section!', 'Write Section Part Two.'); 60 | $tools->writeBlockBasic('Write Block Basic!'); 61 | $tools->writeBlockCommand('Write Block Command!'); 62 | $tools->writeInfo('nl:'); 63 | $tools->nl(); 64 | $output->writeln($tools->getQuestion('Get Question', 'default')); 65 | 66 | $output->write($tools->writeInfo('writeCount(5, 10) ', false)); 67 | $tools->writeCount(5, 10); 68 | $tools->nl(); 69 | $tools->writeDfList('Write Df List', 'Write Df List Part Two.'); 70 | $tools->writeDfList('Write Df List 2', 'Write Df List Part Two 2.'); 71 | $tools->nl(); 72 | $tools->writeError('Write Error and exit!'); 73 | 74 | return static::SUCCESS; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/Commands/Common/DebugCommand.php: -------------------------------------------------------------------------------- 1 | setName('debug') 23 | ->setDescription('Change ProcessWire debug mode.') 24 | ->addOption('on', null, InputOption::VALUE_NONE, 'Turn debug mode on.') 25 | ->addOption('off', null, InputOption::VALUE_NONE, 'Turn debig mode off.'); 26 | } 27 | 28 | /** 29 | * @param InputInterface $input 30 | * @param OutputInterface $output 31 | * @return int|null|void 32 | */ 33 | protected function execute(InputInterface $input, OutputInterface $output) { 34 | $this->init($input, $output); 35 | $tools = new Tools($output); 36 | $tools 37 | ->setInput($input) 38 | ->setHelper($this->getHelper('question')) 39 | ->writeBlockCommand($this->getName()); 40 | 41 | // get new state 42 | $state = false; 43 | if ($input->getOption('on')) { 44 | $state = true; 45 | } elseif (!$input->getOption('off')) { 46 | // if none provided, ask! 47 | $state = $tools->askConfirmation(null, 'Turn debug mode on (type `y` or `n`)?'); 48 | $tools->nl(); 49 | } 50 | 51 | $conf = \ProcessWire\wire('config')->paths->site . 'config.php'; 52 | $newMode = '$config->debug = ' . var_export($state, true) . ";\n"; 53 | $result = ''; 54 | $hasBeenFound = false; 55 | foreach (file($conf) as $line) { 56 | if (!$hasBeenFound && substr($line, 0, 14) == '$config->debug') { 57 | $result .= $newMode; 58 | $hasBeenFound = true; 59 | } else { 60 | $result .= $line; 61 | } 62 | } 63 | 64 | // no debug statement was found 65 | // add it to the bottom of the file 66 | if (!$hasBeenFound) $result .= $newMode; 67 | 68 | file_put_contents($conf, $result); 69 | 70 | $tools->writeSuccess(sprintf('Debug mode has been turned %s.', $state ? 'on' : 'off')); 71 | 72 | return static::SUCCESS; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Commands/Common/ServeCommand.php: -------------------------------------------------------------------------------- 1 | setName('serve') 31 | ->setDescription('Serve ProcessWire via built in PHP webserver') 32 | ->addOption('scheme', null, InputOption::VALUE_OPTIONAL, 'Scheme to serve on', 'http') 33 | ->addOption('host', null, InputOption::VALUE_OPTIONAL, 'Host to serve on', 'localhost') 34 | ->addOption('port', null, InputOption::VALUE_OPTIONAL, 'Port to serve on', '8080'); 35 | } 36 | 37 | /** 38 | * @param InputInterface $input 39 | * @param OutputInterface $output 40 | * @return int|null|void 41 | */ 42 | protected function execute(InputInterface $input, OutputInterface $output) { 43 | $this->checkForProcessWire($output); 44 | 45 | $scheme = $input->getOption('scheme'); 46 | $host = $input->getOption('host'); 47 | $port = $input->getOption('port'); 48 | $url = "$scheme://$host:$port"; 49 | $this->tools->writeComment("Starting PHP server at $url ..."); 50 | $this->helper = $this->getHelper('question'); 51 | 52 | $port = (int)$port; 53 | $pf = @fsockopen($host, $port); // Try to open a socket 54 | if (is_resource($pf)) { 55 | fclose($pf); // Close the previous socket 56 | do { 57 | $this->tools->writeError("Port $port is already in use."); 58 | $this->tools->nl(); 59 | 60 | $port++; // Increment the port number and try again 61 | 62 | $question = new Question($this->tools->getQuestion("Trying another one", (int)$port), $port); 63 | $question->setValidator(function ($answer) { 64 | if ($answer && !filter_var($answer, FILTER_VALIDATE_INT)) { 65 | throw new \RuntimeException('Please enter a valid port number.'); 66 | } 67 | return $answer; 68 | }); 69 | 70 | $newPort = $this->helper->ask($input, $output, $question); // Ask for a new port number 71 | if (is_resource($pf)) { // If the previous socket is still open, close it 72 | fclose($pf); 73 | } 74 | $pf = @fsockopen($host, (int)$newPort); 75 | } 76 | while($pf !== false); 77 | 78 | if (is_resource($pf)) { // If the previous socket is still open, close it 79 | fclose($pf); 80 | } 81 | $port = $newPort; 82 | } 83 | 84 | $url = "$scheme://$host:$port"; 85 | $this->tools->nl(); 86 | $this->tools->writeSuccess("PHP server started, serving at $url. Press CTRL+C to stop the server."); 87 | $this->tools->nl(); 88 | 89 | if (passthru("php -S $host:$port") !== null) { 90 | $this->tools->writeError("Failed to start PHP server."); 91 | return static::FAILURE; 92 | } 93 | 94 | return static::SUCCESS; 95 | } 96 | } -------------------------------------------------------------------------------- /src/Commands/Common/StatusCommand.php: -------------------------------------------------------------------------------- 1 | setName('status') 33 | ->setDescription('Returns versions, paths and environment info') 34 | ->addOption('image', null, InputOption::VALUE_NONE, 'get Diagnose for Imagehandling') 35 | ->addOption('php', null, InputOption::VALUE_NONE, 'get Diagnose for PHP') 36 | ->addOption('pass', null, InputOption::VALUE_NONE, 'display database password'); 37 | } 38 | 39 | /** 40 | * @param InputInterface $input 41 | * @param OutputInterface $output 42 | * @return int|null|void 43 | */ 44 | protected function execute(InputInterface $input, OutputInterface $output) { 45 | $this->init($input, $output); 46 | $this->tools = new Tools($output); 47 | $tables = new Tables($output); 48 | $stTables = array(); 49 | 50 | $this->tools->writeBlockCommand($this->getName()); 51 | 52 | $pwStatus = $this->getPWStatus($input->getOption('pass')); 53 | $wsStatus = $this->getWsStatus(); 54 | 55 | $stTables[] = $tables->buildTable($pwStatus, 'ProcessWire'); 56 | $stTables[] = $tables->buildTable($wsStatus, 'wire-cli'); 57 | 58 | if ($input->getOption('php')) { 59 | $phpStatus = $this->getDiagnosePHP(); 60 | $stTables[] = $tables->buildTable($phpStatus, 'PHP Diagnostics'); 61 | } 62 | 63 | if ($input->getOption('image')) { 64 | $phpStatus = $this->getDiagnoseImagehandling(); 65 | $stTables[] = $tables->buildTable($phpStatus, 'Image Diagnostics'); 66 | } 67 | 68 | $tables->renderTables($stTables); 69 | 70 | return static::SUCCESS; 71 | } 72 | 73 | /** 74 | * @return array 75 | */ 76 | protected function getPWStatus($showPass) { 77 | $config = \ProcessWire\wire('config'); 78 | $on = $this->tools->writeInfo('On', false); 79 | $off = $this->tools->writeComment('Off', false); 80 | $none = $this->tools->writeComment('None', false); 81 | 82 | $version = $config->version; 83 | $latestVersion = parent::getVersion(); // master 84 | if (version_compare($version, $latestVersion, '>')) { 85 | $latestVersion = parent::getVersion('', self::BRANCH_DEV); // dev 86 | $version .= ' ' . self::BRANCH_DEV; 87 | } 88 | 89 | if ($version !== $latestVersion) { 90 | $version .= ' ' . $this->tools->writeMark("(upgrade available: $latestVersion)", false); 91 | } 92 | 93 | $adminUrl = $this->tools->writeLink($this->getAdminUrl(), false); 94 | $advancedMode = $config->advanced ? $on : $off; 95 | $debugMode = $config->debug ? $on : $off; 96 | $timezone = $config->timezone; 97 | $hosts = implode(", ", $config->httpHosts); 98 | $adminTheme = $config->defaultAdminTheme; 99 | $dbHost = $config->dbHost; 100 | $dbName = $config->dbName; 101 | $dbUser = $config->dbUser; 102 | $dbPass = $showPass ? $config->dbPass : '*****'; 103 | $dbPort = $config->dbPort; 104 | 105 | $prepended = trim($config->prependTemplateFile); 106 | $appended = trim($config->appendTemplateFile); 107 | $prependedTemplateFile = $prepended != '' ? $prepended : $none; 108 | $appendedTemplateFile = $appended != '' ? $appended : $none; 109 | 110 | $installPath = getcwd(); 111 | 112 | $status = [ 113 | ['Version', $version], 114 | ['Admin URL', $adminUrl], 115 | ['Advanced mode', $advancedMode], 116 | ['Debug mode', $debugMode], 117 | ['Timezone', $timezone], 118 | ['HTTP hosts', $hosts], 119 | ['Admin theme', $adminTheme], 120 | ['Prepended template file', $prependedTemplateFile], 121 | ['Appended template file', $appendedTemplateFile], 122 | ['Database host', $dbHost], 123 | ['Database name', $dbName], 124 | ['Database user', $dbUser], 125 | ['Database password', $dbPass], 126 | ['Database port', $dbPort], 127 | ['Installation path', $installPath] 128 | ]; 129 | 130 | return $status; 131 | } 132 | 133 | /** 134 | * @return array 135 | */ 136 | protected function getWsStatus() { 137 | return array( 138 | array('Version', $this->getApplication()->getVersion()), 139 | array('Documentation', $this->tools->writeLink('https://docs.sekretservices.com/wire-cli', false)), 140 | array('License', 'MIT') 141 | ); 142 | } 143 | 144 | /** 145 | * @return string 146 | */ 147 | protected function getAdminUrl() { 148 | $admin = \ProcessWire\wire('pages')->get('template=admin'); 149 | $url = \ProcessWire\wire('config')->urls->admin; 150 | 151 | if (!($admin instanceof \ProcessWire\NullPage) && isset($admin->httpUrl)) { 152 | $url = $admin->httpUrl; 153 | } 154 | 155 | return $url; 156 | } 157 | 158 | /** 159 | * wrapper method for the Diagnose PHP submodule from @netcarver 160 | */ 161 | protected function getDiagnosePHP() { 162 | $sub = new DiagnosePhp(); 163 | $rows = $sub->GetDiagnostics(); 164 | $result = []; 165 | 166 | foreach ($rows as $row) { 167 | $result[] = [$row['title'], $row['value']]; 168 | } 169 | 170 | return $result; 171 | } 172 | 173 | /** 174 | * wrapper method for the Diagnose Imagehandling submodule from @netcarver & @horst 175 | */ 176 | protected function getDiagnoseImagehandling() { 177 | $sub = new DiagnoseImagehandling(); 178 | $rows = $sub->GetDiagnostics(); 179 | $result = []; 180 | 181 | foreach ($rows as $row) { 182 | $result[] = [$row['title'], $row['value']]; 183 | } 184 | 185 | return $result; 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/Commands/Common/ViteCommand.php: -------------------------------------------------------------------------------- 1 | setName('vite') 27 | ->setDescription('⚡ Run Vite commands') 28 | ->addArgument('dev', InputArgument::OPTIONAL, 'Run dev server') 29 | ->addOption('v', null, InputOption::VALUE_NONE, 'verbose'); 30 | } 31 | 32 | /** 33 | * @param InputInterface $input 34 | * @param OutputInterface $output 35 | * @return int|null|void 36 | */ 37 | protected function execute(InputInterface $input, OutputInterface $output) { 38 | $output->writeln("Starting Vite server..."); 39 | passthru("npm run start"); 40 | 41 | return static::SUCCESS; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Commands/Field/FieldCloneCommand.php: -------------------------------------------------------------------------------- 1 | setName('field:clone') 30 | ->setDescription('Clones a field') 31 | ->addArgument('field', InputArgument::OPTIONAL) 32 | ->addOption('name', null, InputOption::VALUE_REQUIRED, 'Name'); 33 | } 34 | 35 | /** 36 | * @param InputInterface $input 37 | * @param OutputInterface $output 38 | * @return int|null|void 39 | */ 40 | protected function execute(InputInterface $input, OutputInterface $output) { 41 | $this->init($input, $output); 42 | $tools = new Tools($output); 43 | $tools 44 | ->setInput($input) 45 | ->setHelper($this->getHelper('question')) 46 | ->writeBlockCommand($this->getName()); 47 | 48 | // get the fields 49 | $availableFields = array(); 50 | $fields = \ProcessWire\wire('fields'); 51 | foreach ($fields as $field) { 52 | $availableFields[] = $field->name; 53 | } 54 | 55 | $field = $tools->askChoice($input->getArgument('field'), 'Select field', $availableFields, 0); 56 | $fieldToClone = $fields->get($field); 57 | 58 | if (is_null($fieldToClone)) { 59 | $tools->writeError("Field '{$field}' does not exist!"); 60 | exit(1); 61 | } 62 | 63 | $clone = $fields->clone($fieldToClone); 64 | 65 | if ($input->getOption('name')) { 66 | $clone->name = $input->getOption('name'); 67 | $clone->label = ucfirst($input->getOption('name')); 68 | $clone->save(); 69 | } 70 | 71 | $name = $input->getOption('name') !== '' ? $input->getOption('name') : $field . 'cloned'; 72 | 73 | $tools->writeSuccess("Field '{$field}' cloned successfully."); 74 | 75 | return static::SUCCESS; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/Commands/Field/FieldCreateCommand.php: -------------------------------------------------------------------------------- 1 | setName('field:create') 29 | ->setDescription('Creates a field') 30 | ->addArgument('name', InputArgument::OPTIONAL) 31 | ->addOption('label', null, InputOption::VALUE_REQUIRED, 'Label') 32 | ->addOption('desc', null, InputOption::VALUE_REQUIRED, 'Description') 33 | ->addOption('tag', null, InputOption::VALUE_REQUIRED, 'Tag') 34 | ->addOption('type', null, InputOption::VALUE_REQUIRED, 35 | 'Type of field: text|textarea|email|datetime|checkbox|file|float|image|integer|page|url'); 36 | } 37 | 38 | /** 39 | * @param InputInterface $input 40 | * @param OutputInterface $output 41 | * @return int|null|void 42 | */ 43 | protected function execute(InputInterface $input, OutputInterface $output) { 44 | $this->init($input, $output); 45 | $tools = new Tools($output); 46 | $tools 47 | ->setInput($input) 48 | ->setHelper($this->getHelper('question')) 49 | ->writeBlockCommand($this->getName()); 50 | 51 | $name = $tools->ask($input->getArgument('name'), 'New field name', null, false, null, 'required'); 52 | $label = $tools->ask($input->getOption('label'), 'New field label', $name); 53 | $suppliedType = $tools->askChoice($input->getOption('type'), 'Field type', PWTools::getAvailableFieldtypesShort(), '0'); 54 | $tools->nl(); 55 | 56 | $type = PwTools::getProperFieldtypeName($suppliedType); 57 | $check = $this->checkFieltype($type); 58 | 59 | if ($check === true) { 60 | $field = new Field(); 61 | $field->type = \ProcessWire\wire('modules')->get($type); 62 | $field->name = $name; 63 | $field->label = $label; 64 | $field->description = $input->getOption('desc'); 65 | if ($input->getOption('tag')) $field->tags = $input->getOption('tag'); 66 | $field->save(); 67 | 68 | // add FieldsetClose if tab / fieldset 69 | if (in_array($suppliedType, array('fieldset', 'tab'))) { 70 | $field = new Field(); 71 | $field->type = \ProcessWire\wire('modules')->get('FieldtypeFieldsetClose'); 72 | $field->name = "{$name}_END"; 73 | $field->label = 'Close an open fieldset'; 74 | $field->description = $input->getOption('desc'); 75 | if ($input->getOption('tag')) $field->tags = $input->getOption('tag'); 76 | $field->save(); 77 | } 78 | 79 | $tools->writeSuccess("Field '{$name}' ($type) created successfully."); 80 | } else { 81 | $tools->writeError("This fieldtype `$type` does not exists."); 82 | } 83 | 84 | return static::SUCCESS; 85 | } 86 | 87 | /** 88 | * @param $type 89 | */ 90 | protected function checkFieltype($type) { 91 | // get available fieldtypes 92 | $fieldtypes = array(); 93 | foreach (\ProcessWire\wire('modules') as $module) { 94 | if (preg_match('/^Fieldtype/', $module->name)) { 95 | $fieldtypes[] = $module->name; 96 | } 97 | } 98 | 99 | // check whether fieldtype exists 100 | return in_array($type, $fieldtypes) ? true : false; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Commands/Field/FieldDeleteCommand.php: -------------------------------------------------------------------------------- 1 | setName('field:delete') 30 | ->setDescription('Deletes fields') 31 | ->addArgument('field', InputArgument::OPTIONAL, 'Comma separated list.'); 32 | } 33 | 34 | /** 35 | * @param InputInterface $input 36 | * @param OutputInterface $output 37 | * @return int|null|void 38 | */ 39 | protected function execute(InputInterface $input, OutputInterface $output) { 40 | $this->init($input, $output); 41 | $tools = new Tools($output); 42 | $tools 43 | ->setInput($input) 44 | ->setHelper($this->getHelper('question')) 45 | ->writeBlockCommand($this->getName()); 46 | 47 | // ask 48 | $fields = \ProcessWire\wire('fields'); 49 | $availableFields = array(); 50 | foreach ($fields as $field) { 51 | // exclude system fields 52 | if ($field->flags & Field::flagSystem || $field->flags & Field::flagPermanent) continue; 53 | $availableFields[] = $field->name; 54 | } 55 | 56 | $inputFields = $input->getArgument('field') ? explode(',', $input->getArgument('field')) : null; 57 | $inputFields = $tools->askChoice($inputFields, 'Select all fields which should be deleted', $availableFields, 0, true); 58 | $tools->nl(); 59 | 60 | foreach ($inputFields as $field) { 61 | $fieldToDelete = $fields->get($field); 62 | 63 | if (is_null($fieldToDelete)) { 64 | $tools->writeError("> Field '{$field}' does not exist."); 65 | $tools->nl(); 66 | continue; 67 | } 68 | 69 | try { 70 | $fields->delete($fieldToDelete); 71 | $tools->writeSuccess(" > Field '{$field}' deleted successfully."); 72 | $tools->nl(); 73 | } catch (\WireException $e) { 74 | $tools->writeError("> {$e->getMessage()}"); 75 | $tools->nl(); 76 | } 77 | } 78 | 79 | return static::SUCCESS; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/Commands/Field/FieldEditCommand.php: -------------------------------------------------------------------------------- 1 | setName('field:edit') 30 | ->setDescription('Edit a field') 31 | ->addArgument('field', InputArgument::OPTIONAL) 32 | ->addOption('name', null, InputOption::VALUE_REQUIRED, 'Change field name') 33 | ->addOption('description', null, InputOption::VALUE_OPTIONAL, 'Change field description') 34 | ->addOption('notes', null, InputOption::VALUE_OPTIONAL, 'Change field description') 35 | ->addOption('label', null, InputOption::VALUE_REQUIRED, 'Change label'); 36 | } 37 | 38 | /** 39 | * @param InputInterface $input 40 | * @param OutputInterface $output 41 | * @return int|null|void 42 | */ 43 | protected function execute(InputInterface $input, OutputInterface $output) { 44 | $this->init($input, $output); 45 | $tools = new Tools($output); 46 | $tools 47 | ->setInput($input) 48 | ->setHelper($this->getHelper('question')) 49 | ->writeBlockCommand($this->getName()); 50 | 51 | $fields = \ProcessWire\wire('fields'); 52 | 53 | $field = $tools->ask($input->getArgument('field'), 'Field name', null, false, null, 'required'); 54 | $fieldToEdit = $fields->get($field); 55 | 56 | if (is_null($fieldToEdit)) { 57 | $tools->writeError("Field '{$field}' does not exist."); 58 | exit(1); 59 | } 60 | 61 | $name = $tools->ask($input->getOption('name'), 'New field name', $fieldToEdit->name); 62 | $label = $tools->ask($input->getOption('label'), 'New field label', $fieldToEdit->label); 63 | $description = $input->getOption('description') ? $input->getOption('description') : null; 64 | $notes = $input->getOption('notes') ? $input->getOption('notes') : null; 65 | 66 | if ($name && $name !== $fieldToEdit->name) $fieldToEdit->name = $name; 67 | if ($label && $label !== $fieldToEdit->label) $fieldToEdit->label = ucfirst($label); 68 | if ($description !== $fieldToEdit->description) $fieldToEdit->description = $description; 69 | if ($notes !== $fieldToEdit->notes) $fieldToEdit->notes = $notes; 70 | 71 | $fieldToEdit->save(); 72 | 73 | $tools->writeSuccess("Field '{$field}' edited successfully."); 74 | 75 | return static::SUCCESS; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/Commands/Field/FieldListCommand.php: -------------------------------------------------------------------------------- 1 | setName('field:list') 29 | ->setDescription('Lists all available fields.') 30 | ->addOption('all', null, InputOption::VALUE_NONE, 'Show built-in fields. By default, system/permanent fields are not shown.') 31 | ->addOption('template', null, InputOption::VALUE_REQUIRED, 'Filter by template. When selected, only the fields from a specific template will be shown.') 32 | ->addOption('tag', null, InputOption::VALUE_REQUIRED, 'Filter by tag. When selected, only the fields with a specific tag will be shown.') 33 | ->addOption('type', null, InputOption::VALUE_REQUIRED, 'Filter by field type. When specified, only fields of the selected type will be shown.') 34 | ->addOption('unused', null, InputOption::VALUE_NONE, 'Only list unused fields.'); 35 | } 36 | 37 | /** 38 | * @param InputInterface $input 39 | * @param OutputInterface $output 40 | * @return int|null|void 41 | */ 42 | protected function execute(InputInterface $input, OutputInterface $output) { 43 | $this->init($input, $output); 44 | $tools = new Tools($output); 45 | $tools->writeBlockCommand($this->getName()); 46 | 47 | // get available fields 48 | $fieldtypes = array(); 49 | foreach (\ProcessWire\wire('modules') as $module) { 50 | if (preg_match('/^Fieldtype/', $module->name)) { 51 | $fieldtypes[] = $module->name; 52 | } 53 | } 54 | 55 | $headers = array('Name', 'Label', 'Type', 'Templates'); 56 | $data = $this->getData($this->getFilter($input)); 57 | 58 | if (count($data->count) > 0) { 59 | foreach ($data->content as $tag => $c) { 60 | $tools->writeInfo('--- ' . strtoupper($tag) . ' ---'); 61 | $tables = new Tables($output); 62 | $fieldTables = array($tables->buildTable($c, $headers)); 63 | $tables->renderTables($fieldTables, false); 64 | } 65 | } 66 | 67 | $tools->writeCount($data->count); 68 | 69 | return static::SUCCESS; 70 | } 71 | 72 | /** 73 | * @param InputInterface $input 74 | * @return array 75 | */ 76 | private function getFilter($input) { 77 | $filter = array(); 78 | $filter['all'] = $input->getOption('all') ? true : false; 79 | $filter['unused'] = $input->getOption('unused') ? true : false; 80 | $filter['template'] = $input->getOption('template') ? $input->getOption('template') : ''; 81 | $filter['tag'] = $input->getOption('tag') ? $input->getOption('tag') : ''; 82 | $filter['type'] = $input->getOption('type') ? PwTools::getProperFieldtypeName($input->getOption('type')) : ''; 83 | 84 | return (object)$filter; 85 | } 86 | 87 | /** 88 | * get templates data 89 | * 90 | * @param object $filter 91 | * @return array 92 | */ 93 | private function getData($filter) { 94 | $content = array(); 95 | $count = 0; 96 | 97 | foreach (\ProcessWire\wire('fields') as $field) { 98 | // no filter, exclude built-in fields except title 99 | if ($filter->all === false && ($field->flags & Field::flagSystem || $field->flags & Field::flagPermanent)) { 100 | if ($field->name != 'title') continue; 101 | } 102 | 103 | // filter unused fields 104 | if ($filter->unused && $field->getTemplates()->count()) continue; 105 | 106 | // filter by template 107 | if ($filter->template && !$field->getTemplates()->has($filter->template)) continue; 108 | 109 | // filter by tag 110 | if ($filter->tag && !$this->fieldHasTag($field->tags, $filter->tag)) continue; 111 | 112 | // filter by field type 113 | if ($filter->type && $field->type != $filter->type) continue; 114 | 115 | // get row content 116 | $fieldContent = array( 117 | $field->name, 118 | $field->label, 119 | str_replace('Fieldtype', '', $field->type), 120 | $field->getTemplates()->count 121 | ); 122 | 123 | // add field by tag 124 | if (!$field->tags) { 125 | $tag = 'untagged'; 126 | 127 | if (!isset($content[$tag])) $content[$tag] = array(); 128 | $content[$tag][$field->name] = $fieldContent; 129 | } else { 130 | $tags = explode(' ', $field->tags); 131 | 132 | foreach ($tags as $tag) { 133 | if (!$tag) continue; 134 | $tag = strtolower(ltrim($tag, '-')); 135 | if (substr($tag, 0, 1) === '-') ltrim($tag, '-'); 136 | 137 | if (!isset($content[$tag])) $content[$tag] = array(); 138 | $content[$tag][$field->name] = $fieldContent; 139 | } 140 | } 141 | 142 | $count++; 143 | } 144 | 145 | ksort($content); 146 | return (object) array('count' => $count, 'content' => $content); 147 | } 148 | 149 | /** 150 | * check whether field has specific tag 151 | * 152 | * @param string $fieldTags 153 | * @param string $tagName 154 | * @return boolean 155 | */ 156 | protected function fieldHasTag($fieldTags, $tagName) { 157 | $tags = explode(',', $fieldTags); 158 | 159 | if ($tags && in_array($tagName, $tags)) { 160 | $hasTag = true; 161 | } 162 | 163 | return isset($hasTag) ? $hasTag : false; 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/Commands/Field/FieldRenameCommand.php: -------------------------------------------------------------------------------- 1 | setName('field:rename') 30 | ->setDescription('Rename a field') 31 | ->addArgument('field', InputArgument::OPTIONAL) 32 | ->addOption('name', null, InputOption::VALUE_OPTIONAL, 'Change field name') 33 | ->addOption('tag', null, InputOption::VALUE_OPTIONAL, 'Restrict field list by tag') 34 | ->addOption('camelCaseToSnakeCase', null, InputOption::VALUE_NONE, 'Change field name notation') 35 | ->addOption('chooseAll', null, InputOption::VALUE_NONE, 'Choose all fields by default'); 36 | } 37 | 38 | /** 39 | * @param InputInterface $input 40 | * @param OutputInterface $output 41 | * @return int|null|void 42 | */ 43 | protected function execute(InputInterface $input, OutputInterface $output) { 44 | $this->init($input, $output); 45 | $tools = new Tools($output); 46 | $tools 47 | ->setInput($input) 48 | ->setHelper($this->getHelper('question')) 49 | ->writeBlockCommand($this->getName()); 50 | 51 | // get the fields 52 | $availableFields = array(); 53 | $fields = \ProcessWire\wire('fields'); 54 | $fieldsRestricted = $fields; 55 | 56 | // restrict selection 57 | if ($tag = $input->getOption('tag')) $fieldsRestricted = $fields->findByTag($tag); 58 | 59 | // select fields 60 | foreach ($fieldsRestricted as $field) $availableFields[] = $field->name; 61 | $preselect = $input->getOption('chooseAll') ? implode(',', array_keys($availableFields)) : 0; 62 | $chosenFields = $tools->askChoice($input->getArgument('field'), 'Select field', $availableFields, $preselect, true); 63 | if (!is_array($chosenFields)) $chosenFields = array($chosenFields); 64 | 65 | foreach ($chosenFields as $field) { 66 | $fieldToEdit = $fields->get($field); 67 | 68 | if (is_null($fieldToEdit)) { 69 | $tools->writeError("Field '{$field}' does not exist."); 70 | exit(1); 71 | } 72 | 73 | // rename single! if name is present 74 | if (count($chosenFields) === 1 && $newName = $input->getOption('name')) { 75 | $name = $tools->ask($newName, 'New field name', $fieldToEdit->name); 76 | if ($name && $name !== $fieldToEdit->name) $fieldToEdit->name = $name; 77 | } else if ($input->getOption('camelCaseToSnakeCase')) { 78 | // transform fieldname from camelCase to snake_case 79 | $fieldToEdit->name = $this->camelCaseToSnakeCase($fieldToEdit->name); 80 | } 81 | 82 | $fieldToEdit->save(); 83 | $tools->writeSuccess("Field '{$field}' renamed successfully to '{$fieldToEdit->name}'."); 84 | } 85 | 86 | return static::SUCCESS; 87 | } 88 | 89 | /** 90 | * camelCase to snake_case 91 | * 92 | * @param string $string 93 | * @return string 94 | */ 95 | private function camelCaseToSnakeCase($string) { 96 | return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $string)); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Commands/Field/FieldTagCommand.php: -------------------------------------------------------------------------------- 1 | setName('field:tag') 26 | ->setDescription('Tags fields') 27 | ->addArgument('field', InputArgument::OPTIONAL, 'Comma separated list.') 28 | ->addOption('tag', null, InputOption::VALUE_REQUIRED, 'Tag name'); 29 | } 30 | 31 | /** 32 | * @param InputInterface $input 33 | * @param OutputInterface $output 34 | * @return int|null|void 35 | */ 36 | protected function execute(InputInterface $input, OutputInterface $output) { 37 | $this->init($input, $output); 38 | $tools = new Tools($output); 39 | $tools 40 | ->setInput($input) 41 | ->setHelper($this->getHelper('question')) 42 | ->writeBlockCommand($this->getName()); 43 | 44 | $fields = \ProcessWire\wire('fields'); 45 | 46 | $inputFields = $tools->ask($input->getArgument('field'), 'Field name(s), comma-separated', null, false, null, 'required'); 47 | $tag = $tools->ask($input->getOption('tag'), 'Tag name', null, false, null, 'required'); 48 | 49 | foreach (explode(',', $inputFields) as $field) { 50 | $fieldToTag = $fields->get($field); 51 | $tools->nl(); 52 | 53 | if (is_null($fieldToTag)) { 54 | $tools->writeError(" > Field '{$field}' does not exist."); 55 | continue; 56 | } 57 | 58 | try { 59 | $fieldToTag->tags = $tag; 60 | $fieldToTag->save(); 61 | $tools->writeSuccess(" > Field '{$field}' edited successfully."); 62 | } catch (\WireException $e) { 63 | $tools->writeError(" > {$e->getMessage()}"); 64 | } 65 | } 66 | 67 | return static::SUCCESS; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/Commands/Field/FieldTypesCommand.php: -------------------------------------------------------------------------------- 1 | setName('field:types') 26 | ->setDescription('Lists all available fieldtypes.'); 27 | } 28 | 29 | /** 30 | * @param InputInterface $input 31 | * @param OutputInterface $output 32 | * @return int|null|void 33 | */ 34 | protected function execute(InputInterface $input, OutputInterface $output) { 35 | $this->init($input, $output); 36 | $tools = new Tools($output); 37 | $tools->writeBlockCommand($this->getName()); 38 | 39 | // get available fieldtypes 40 | foreach (\ProcessWire\wire('modules') as $module) { 41 | if (preg_match('/^Fieldtype/', $module->name)) { 42 | $tools->writeDfList($module->name, substr($module->name, 9)); 43 | } 44 | } 45 | 46 | return static::SUCCESS; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Commands/Logs/LogListCommand.php: -------------------------------------------------------------------------------- 1 | setName('log:list') 27 | ->setDescription('List available log files'); 28 | } 29 | 30 | /** 31 | * @param InputInterface $input 32 | * @param OutputInterface $output 33 | * @return int|null|void 34 | */ 35 | protected function execute(InputInterface $input, OutputInterface $output) { 36 | $this->init($input, $output); 37 | $tools = new Tools($output); 38 | $logs = \ProcessWire\wire('log')->getLogs(); 39 | $tools->writeBlockCommand($this->getName()); 40 | 41 | $data = array(); 42 | foreach ($logs as $log) { 43 | $data[] = array( 44 | $log['name'], 45 | \ProcessWire\wireRelativeTimeStr($log['modified']), 46 | \ProcessWire\wire('log')->getTotalEntries($log['name']), 47 | \ProcessWire\wireBytesStr($log['size']) 48 | ); 49 | } 50 | 51 | $headers = array('Name', 'Modified', 'Entries', 'Size'); 52 | $tables = new Tables($output); 53 | $logTables = array($tables->buildTable($data, $headers)); 54 | $tables->renderTables($logTables, false); 55 | $count = count($logs); 56 | $tools->writeCount($count); 57 | 58 | return static::SUCCESS; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Commands/Logs/LogTailCommand.php: -------------------------------------------------------------------------------- 1 | setName('log:tail') 28 | ->setDescription('Log Output') 29 | ->addArgument('name', InputArgument::OPTIONAL, 'Name of the log file.') 30 | ->addOption('limit', null, InputOption::VALUE_REQUIRED, 'Specify number of lines. Default: 10. (int)') 31 | ->addOption('text', null, InputOption::VALUE_REQUIRED, 'Text to find. (string)') 32 | ->addOption('from', null, InputOption::VALUE_REQUIRED, 'Oldest date to match entries. (int|string)') 33 | ->addOption('to', null, InputOption::VALUE_REQUIRED, 'Newest date to match entries. (int|string)'); 34 | } 35 | 36 | /** 37 | * @param InputInterface $input 38 | * @param OutputInterface $output 39 | * @return int|null|void 40 | */ 41 | protected function execute(InputInterface $input, OutputInterface $output) { 42 | $this->init($input, $output); 43 | $tools = new Tools($output); 44 | $helper = $this->getHelper('question'); 45 | $formatter = $this->getHelper('formatter'); 46 | $log = \ProcessWire\wire('log'); 47 | $availableLogs = $log->getLogs(); 48 | $tools->writeBlockCommand($this->getName()); 49 | 50 | $question = new ChoiceQuestion( 51 | $tools->getQuestion('Please choose one of', key($availableLogs)), 52 | array_keys($availableLogs), 53 | 0 54 | ); 55 | 56 | $name = $input->getArgument('name'); 57 | if (!$name) { 58 | $name = $helper->ask($input, $output, $question); 59 | } else if (!array_key_exists($name, $availableLogs)) { 60 | $tools->writeError(" Log '{$name}' does not exist."); 61 | $tools->nl(); 62 | $name = $helper->ask($input, $output, $question); 63 | } 64 | 65 | $tools->nl(); 66 | $tools->writeHeader('Log ' . ucfirst($name)); 67 | $tools->nl(); 68 | 69 | $options = array( 70 | 'limit' => $input->getOption('limit') ? $input->getOption('limit') : 10, 71 | 'dateTo' => $input->getOption('to') ? $input->getOption('to') : 'now', 72 | 'dateFrom' => $input->getOption('from') ? $input->getOption('from') : '-10years', 73 | 'text' => $input->getOption('text') ? $input->getOption('text') : '' 74 | ); 75 | 76 | $headers = array('Date', 'User', 'URL', 'Message'); 77 | $data = $log->getEntries($name, $options); 78 | 79 | $tables = new Tables($output); 80 | $logTables = array($tables->buildTable($data, $headers)); 81 | $tables->renderTables($logTables, false); 82 | 83 | $count = count($data); 84 | $total = $log->getTotalEntries($name); 85 | $tools->writeCount($count, $total); 86 | 87 | return static::SUCCESS; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Commands/Module/ModuleDisableCommand.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ModuleDisableCommand extends PwConnector { 20 | 21 | /** 22 | * Configures the current command. 23 | */ 24 | protected function configure() { 25 | $this 26 | ->setName('module:disable') 27 | ->setDescription('Disable provided module(s)') 28 | ->addArgument('modules', InputArgument::IS_ARRAY, 'Module classname (separate multiple names with a space)') 29 | ->addOption('rm', null, InputOption::VALUE_NONE, 'Remove module'); 30 | } 31 | 32 | /** 33 | * @param InputInterface $input 34 | * @param OutputInterface $output 35 | * @return int|null|void 36 | */ 37 | protected function execute(InputInterface $input, OutputInterface $output) { 38 | $this->init($input, $output); 39 | $this->tools = new Tools($output); 40 | $this->tools 41 | ->setInput($input) 42 | ->setHelper($this->getHelper('question')) 43 | ->writeBlockCommand($this->getName()); 44 | 45 | $modules = $this->tools->ask( 46 | $input->getArgument('modules'), 47 | $this->getDefinition()->getArgument('modules')->getDescription(), 48 | null, 49 | false, 50 | null, 51 | 'required' 52 | ); 53 | 54 | if (!is_array($modules)) $modules = explode(" ", $modules); 55 | $remove = $input->getOption('rm') === true ? true : false; 56 | 57 | foreach ($modules as $module) { 58 | try { 59 | $this->checkIfModuleExists($module, $remove); 60 | } catch (WireException $e) { 61 | $this->tools->writeError("damn"); 62 | exit(1); 63 | } 64 | 65 | if (\ProcessWire\wire('modules')->uninstall($module)) { 66 | $this->tools->writeSuccess("Module `{$module}` uninstalled successfully."); 67 | } 68 | 69 | // remove module 70 | if ($remove === true && is_dir(\ProcessWire\wire('config')->paths->$module)) { 71 | $this->tools->nl(); 72 | if ($this->recurseRmdir(\ProcessWire\wire('config')->paths->$module)) { 73 | $this->tools->writeInfo("Module `{$module}` was removed successfully."); 74 | } else { 75 | $this->tools->writeError("Module `{$module}` could not be removed could not be removed."); 76 | } 77 | } 78 | } 79 | 80 | return static::SUCCESS; 81 | } 82 | 83 | private function checkIfModuleExists($module, $remove) { 84 | try { 85 | if (!is_dir(\ProcessWire\wire('config')->paths->siteModules . $module)) { 86 | $this->tools->writeError("Module `{$module}` does not exist."); 87 | exit(1); 88 | } 89 | 90 | if (!\ProcessWire\wire('modules')->getModule($module, array('noPermissionCheck' => true, 'noInit' => true)) && $remove === false) { 91 | $this->tools->writeError("Module `{$module}` is not installed."); 92 | exit(1); 93 | } 94 | } catch (\Exception $e) { 95 | $this->tools->writeError("Module `{$module}` does not exist."); 96 | return false; 97 | } 98 | } 99 | 100 | /** 101 | * remove uninstalled module recursive 102 | * 103 | * @param string $dir 104 | * @return boolean 105 | */ 106 | private function recurseRmdir($dir) { 107 | if (is_dir($dir)) { 108 | chmod($dir, 0775); 109 | $files = array_diff(scandir($dir), array('.', '..')); 110 | foreach ($files as $file) { 111 | (is_dir("$dir/$file")) ? $this->recurseRmdir("$dir/$file") : unlink("$dir/$file"); 112 | } 113 | $removed = rmdir($dir); 114 | } else { 115 | $removed = false; 116 | } 117 | 118 | return $removed; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Commands/Module/ModuleDownloadCommand.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ModuleDownloadCommand extends PwModuleTools { 20 | 21 | /** 22 | * @var OutputInterface 23 | */ 24 | protected $output; 25 | 26 | /** 27 | * Configures the current command. 28 | */ 29 | protected function configure() { 30 | $this 31 | ->setName('module:download') 32 | ->setDescription('Downloads ProcessWire module(s).') 33 | ->addArgument('modules', InputArgument::OPTIONAL, 'Provide one or more module class name, comma separated: Foo,Bar') 34 | ->addOption('github', null, InputOption::VALUE_OPTIONAL, 'Download module via github. Use this option if the module isn\'t added to the ProcessWire module directory.') 35 | ->addOption('branch', null, InputOption::VALUE_OPTIONAL, 'Define specific branch to download from.'); 36 | } 37 | 38 | /** 39 | * @param InputInterface $input 40 | * @param OutputInterface $output 41 | * @return int|null|void 42 | */ 43 | protected function execute(InputInterface $input, OutputInterface $output) { 44 | $this->init($input, $output); 45 | $this->tools = new Tools($output); 46 | $this->tools->setHelper($this->getHelper('question')) 47 | ->setInput($input) 48 | ->writeBlockCommand($this->getName()); 49 | 50 | $this->checkPermissions(); 51 | $this->output = $output; 52 | 53 | $modules = $this->tools->ask($input->getArgument('modules'), 'Modules', null, false, null, 'required'); 54 | $github = $input->getOption('github'); 55 | $branch = $input->getOption('branch') ? $input->getOption('branch') : 'master'; 56 | 57 | if ($github) $github = "https://github.com/{$input->getOption('github')}/archive/{$branch}.zip"; 58 | 59 | if (!is_array($modules)) $modules = explode(',', $modules); 60 | foreach ($modules as $module) { 61 | $this->tools->writeBlockBasic(" - Module `$module`: "); 62 | 63 | if ($this->checkIfModuleExists($module)) { 64 | $this->tools->writeError("Module '{$module}' already exists."); 65 | } else { 66 | parent::setModule($module); 67 | // reset PW modules cache 68 | \ProcessWire\wire('modules')->refresh(); 69 | 70 | if (isset($github)) { 71 | $this->downloadModuleByUrl($module, $github); 72 | } else { 73 | $this->downloadModuleIfExists($module); 74 | } 75 | } 76 | } 77 | 78 | \ProcessWire\wire('modules')->refresh(); 79 | 80 | return static::SUCCESS; 81 | } 82 | 83 | /** 84 | * Check Permissions 85 | */ 86 | private function checkPermissions() { 87 | if (!ini_get('allow_url_fopen')) { 88 | // check if we have the rights to download files from other domains 89 | // using copy or file_get_contents 90 | $this->tools->writeError('The php config `allow_url_fopen` is disabled on the server. Enable it then try again.'); 91 | exit(1); 92 | } 93 | 94 | if (!is_writable(\ProcessWire\wire('config')->paths->siteModules)) { 95 | // check if module directory is writeable 96 | $this->tools->writeError('Make sure your `/site/modules` directory is writeable by PHP.'); 97 | exit(1); 98 | } 99 | } 100 | 101 | /** 102 | * check if a module exists in processwire module directory 103 | * 104 | * @param string $module 105 | */ 106 | public function downloadModuleIfExists($module) { 107 | $contents = file_get_contents( 108 | \ProcessWire\wire('config')->moduleServiceURL . 109 | '?apikey=' . \ProcessWire\wire('config')->moduleServiceKey . 110 | '&limit=1' . '&class_name=' . $module 111 | ); 112 | 113 | $result = json_decode($contents); 114 | 115 | if ($result->status === 'error') { 116 | $this->tools->writeError("A module with the class `$module` does not exist."); 117 | } else { 118 | // yeah! module exists 119 | $item = $result->items[0]; 120 | $moduleUrl = "{$item->project_url}/archive/master.zip"; 121 | $this->downloadModuleByUrl($module, $moduleUrl); 122 | } 123 | } 124 | 125 | /** 126 | * download module 127 | * 128 | * @param string $module 129 | */ 130 | public function downloadModuleByUrl($module, $moduleUrl) { 131 | try { 132 | $this 133 | ->downloadModule($moduleUrl) 134 | ->extractModule() 135 | ->cleanUpTmp(); 136 | } catch (\Exception $e) { 137 | $this->tools->writeError($e->getMessage()); 138 | $this->tools->writeError("Could not download module `$module`. Please try again later."); 139 | } 140 | } 141 | 142 | /** 143 | * get the config either default or overwritten by user config 144 | * @param string $key name of the option 145 | * @return mixed return requested option value 146 | */ 147 | public function getConfig($key) { 148 | return self::$defaults[$key]; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/Commands/Module/ModuleEnableCommand.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ModuleEnableCommand extends PwModuleTools { 20 | 21 | /** 22 | * Configures the current command. 23 | */ 24 | protected function configure() { 25 | $this 26 | ->setName('module:enable') 27 | ->setDescription('Enables provided module(s)') 28 | ->addArgument('modules', InputArgument::OPTIONAL, 'Provide one or more module class name, comma separated: Foo,Bar') 29 | ->addOption('github', null, InputOption::VALUE_OPTIONAL, 'Download module via github. Use this option if the module isn\'t added to the ProcessWire module directory.') 30 | ->addOption('branch', null, InputOption::VALUE_OPTIONAL, 'Optional. Define specific branch to download from.'); 31 | } 32 | 33 | /** 34 | * @param InputInterface $input 35 | * @param OutputInterface $output 36 | * @return int|null|void 37 | */ 38 | protected function execute(InputInterface $input, OutputInterface $output) { 39 | $this->init($input, $output); 40 | $this->tools = new Tools($output); 41 | $this->tools->setHelper($this->getHelper('question')) 42 | ->setInput($input) 43 | ->writeBlockCommand($this->getName()); 44 | 45 | $modules = $this->tools->ask($input->getArgument('modules'), 'Modules', null, false, null, 'required'); 46 | if (!is_array($modules)) $modules = explode(',', $modules); 47 | 48 | foreach ($modules as $module) { 49 | // if module doesn't exist, download the module 50 | if (!$this->checkIfModuleExists($module)) { 51 | $this->tools->writeComment("Cannot find '{$module}' locally, trying to download..."); 52 | $this->tools->nl(); 53 | $this->passOnToModuleDownloadCommand($module, $output, $input); 54 | } 55 | 56 | // check whether module is already installed 57 | if (\ProcessWire\wire('modules')->isInstalled($module)) { 58 | $this->tools->writeInfo(" Module `{$module}` is already installed."); 59 | continue; 60 | } 61 | 62 | // install module 63 | $options = array( 64 | 'noPermissionCheck' => true, 65 | 'noInit' => true 66 | ); 67 | 68 | if (\ProcessWire\wire('modules')->getInstall($module, $options)) { 69 | $this->tools->writeSuccess(" Module `{$module}` installed successfully."); 70 | } else { 71 | $this->tools->writeError(" Module `{$module}` does not exist."); 72 | } 73 | } 74 | 75 | return static::SUCCESS; 76 | } 77 | 78 | private function checkIfModuleExistsLocally($module, $output, $input) { 79 | if (!$this->checkIfModuleExists($module)) { 80 | $output->writeln("Cannot find '{$module}' locally, trying to download..."); 81 | $this->passOnToModuleDownloadCommand($module, $output, $input); 82 | } 83 | 84 | } 85 | 86 | private function passOnToModuleDownloadCommand($module, $output, $input) { 87 | $command = $this->getApplication()->find('mod:download'); 88 | 89 | $arguments = array( 90 | 'command' => 'mod:download', 91 | 'modules' => $module, 92 | '--github' => $input->getOption('github'), 93 | '--branch' => $input->getOption('branch') 94 | ); 95 | 96 | $passOnInput = new ArrayInput($arguments); 97 | $command->run($passOnInput, $output); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Commands/Module/ModuleGenerateCommand.php: -------------------------------------------------------------------------------- 1 | client = $client; 31 | parent::__construct(); 32 | } 33 | 34 | /** 35 | * Configures the current command. 36 | */ 37 | protected function configure() { 38 | $this 39 | ->setName('module:generate') 40 | ->setDescription('Generates a boilerplate module') 41 | ->addArgument('name', InputOption::VALUE_REQUIRED, 'Provide a class name for the module') 42 | ->addOption('title', null, InputOption::VALUE_REQUIRED, 'Module title') 43 | ->addOption('mod-version', null, InputOption::VALUE_REQUIRED, 'Module version') 44 | ->addOption('author', null, InputOption::VALUE_REQUIRED, 'Module author') 45 | ->addOption('link', null, InputOption::VALUE_REQUIRED, 'Module link') 46 | ->addOption('summary', null, InputOption::VALUE_REQUIRED, 'Module summary') 47 | ->addOption('type', null, InputOption::VALUE_REQUIRED, 'Module type') 48 | ->addOption('extends', null, InputOption::VALUE_REQUIRED, 'Module extends') 49 | ->addOption('implements', null, InputOption::VALUE_REQUIRED, 'Module implements (Interface)') 50 | ->addOption('require-pw', null, InputOption::VALUE_REQUIRED, 'Module\'s ProcessWire version compatibility') 51 | ->addOption('require-php', null, InputOption::VALUE_REQUIRED, 'Module\'s PHP version compatibility') 52 | ->addOption('is-autoload', null, InputOption::VALUE_NONE, 'autoload = true') 53 | ->addOption('is-singular', null, InputOption::VALUE_NONE, 'singular = true') 54 | ->addOption('is-permanent', null, InputOption::VALUE_NONE, 'permanent = true') 55 | ->addOption('with-external-json', null, InputOption::VALUE_NONE, 'Generates external json config file') 56 | ->addOption('with-copyright', null, InputOption::VALUE_NONE, 'Adds copyright in comments') 57 | ->addOption('with-uninstall', null, InputOption::VALUE_NONE, 'Adds uninstall method') 58 | ->addOption('with-sample-code', null, InputOption::VALUE_NONE, 'Adds sample code') 59 | ->addOption('with-config-page', null, InputOption::VALUE_NONE, 'Adds config page'); 60 | } 61 | 62 | /** 63 | * @param InputInterface $input 64 | * @param OutputInterface $output 65 | * @return int|null|void 66 | */ 67 | protected function execute(InputInterface $input, OutputInterface $output) { 68 | $this->init($input, $output); 69 | $this->tools = new Tools($output); 70 | $this->tools->setHelper($this->getHelper('question')) 71 | ->setInput($input) 72 | ->writeBlockCommand($this->getName()); 73 | 74 | $modName = \ProcessWire\wire('sanitizer')->name($input->getArgument('name')); 75 | if (!$modName) $this->tools->writeErrorAndExit('Please provide a class name for the module.'); 76 | 77 | $request = $this->createRequest($modName, $output, $input); 78 | $modDir = getcwd() . parent::getModuleDirectory(); 79 | 80 | $this->download($request, $modDir) 81 | ->extract($modDir) 82 | ->cleanUp($modDir, $modName); 83 | 84 | return static::SUCCESS; 85 | } 86 | 87 | protected function getDefaults() { 88 | return array( 89 | 'version' => '0.0.1', 90 | 'requirePw' => \ProcessWire\wire('config')->version, 91 | 'requirePhp' => PHP_VERSION 92 | ); 93 | } 94 | 95 | /** 96 | * @param $modName 97 | * @return string 98 | */ 99 | private function createRequest($modName, OutputInterface $output, InputInterface $input) { 100 | if ($this->checkIfModuleExists($modName)) { 101 | $this->tools->writeErrorAndExit("Module '{$modName}' already exists!"); 102 | } 103 | 104 | $defaults = $this->getDefaults(); 105 | $request = self::API . "?name={$modName}"; 106 | $this->tools->write('Generating module at `modules.pw` ..'); 107 | 108 | $params = array( 109 | 'title' => $input->getOption('title'), 110 | 'version' => $input->getOption('mod-version') ? $input->getOption('mod-version') : $defaults['version'], 111 | 'author' => $input->getOption('author'), 112 | 'link' => $input->getOption('link'), 113 | 'summary' => $input->getOption('summary'), 114 | 'type' => $input->getOption('type'), 115 | 'extends' => $input->getOption('extends'), 116 | 'implements' => $input->getOption('implements'), 117 | 'require-pw' => $input->getOption('require-pw') ? $input->getOption('require-pw') : $defaults['requirePw'], 118 | 'require-php' => $input->getOption('require-php') ? $input->getOption('require-php') : $defaults['requirePhp'], 119 | ); 120 | 121 | $paramsBool = array( 122 | 'is-autoload' => $input->getOption('is-autoload'), 123 | 'is-singular' => $input->getOption('is-singular'), 124 | 'is-permanent' => $input->getOption('is-permanent'), 125 | 'with-external-json' => $input->getOption('with-external-json'), 126 | 'with-copyright' => $input->getOption('with-copyright'), 127 | 'with-uninstall' => $input->getOption('with-uninstall'), 128 | 'with-config-page' => $input->getOption('with-config-page') 129 | ); 130 | 131 | foreach ($params as $key => $param) { 132 | if ($param) $request .= "&{$key}={$param}"; 133 | } 134 | 135 | foreach ($paramsBool as $key => $param) { 136 | if ($param) $request .= "&{$key}=true"; 137 | } 138 | 139 | return $request; 140 | } 141 | 142 | /** 143 | * @param $request 144 | * @param $modDir 145 | * @return $this 146 | */ 147 | private function download($request, $modDir) { 148 | $this->tools->write("Downloading ... - {$request}"); 149 | 150 | $response = $this->client->get($request)->getBody(); 151 | file_put_contents("$modDir/temp.zip", $response); 152 | 153 | return $this; 154 | } 155 | 156 | /** 157 | * @param $modDir 158 | * @return $this 159 | */ 160 | private function extract($modDir) { 161 | $this->tools->write('Extracting ...'); 162 | 163 | $archive = new ZipArchive; 164 | $archive->open($modDir . '/temp.zip'); 165 | $archive->extractTo($modDir); 166 | $archive->close(); 167 | 168 | return $this; 169 | } 170 | 171 | /** 172 | * @param $modDir 173 | * @return $this 174 | */ 175 | private function cleanUp($modDir, $modName) { 176 | @chmod($modDir . '/temp.zip', 0777); 177 | @unlink($modDir . '/temp.zip'); 178 | 179 | $this->tools->writeSuccess("Module {$modName} created successfully!"); 180 | 181 | return $this; 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/Commands/Module/ModuleUpgradeCommand.php: -------------------------------------------------------------------------------- 1 | setName('module:upgrade') 32 | ->setDescription('Upgrades given module(s)') 33 | ->addArgument('modules', InputArgument::IS_ARRAY, 'Module classname (separate multiple names with a space)') 34 | ->addOption('check', null, InputOption::VALUE_NONE, 'Just check for module upgrades.'); 35 | } 36 | 37 | /** 38 | * @param InputInterface $input 39 | * @param OutputInterface $output 40 | * @return int|null|void 41 | */ 42 | protected function execute(InputInterface $input, OutputInterface $output) { 43 | $this->init($input, $output); 44 | 45 | if (!\ProcessWire\wire('config')->moduleServiceKey) throw new \RuntimeException('No module service key was found.'); 46 | 47 | $this->tools = new Tools($output); 48 | $this->tools->setHelper($this->getHelper('question')) 49 | ->setInput($input) 50 | ->writeBlockCommand($this->getName()); 51 | 52 | $modules = $input->getArgument('modules'); 53 | 54 | if ($modules && !$input->getOption('check')) { 55 | 56 | // upgrade specific modules 57 | if (!is_array($modules)) $modules = explode(" ", $modules); 58 | if ($modules) $this->upgradeModules($modules, $output); 59 | 60 | } else { 61 | 62 | // check for module upgrades 63 | \ProcessWire\wire('modules')->resetCache(); 64 | 65 | if ($moduleVersions = parent::getModuleVersions($output, true)) { 66 | $this->tools->writeInfo('An upgrade is available for:'); 67 | foreach ($moduleVersions as $name => $info) { 68 | $this->tools->writeDfList($name, "{$info['local']} -> {$info['remote']}"); 69 | } 70 | 71 | // aks which module should be updated 72 | if (!$input->getOption('check')) { 73 | $this->tools->nl(); 74 | $modules = $this->tools->askChoice( 75 | $modules, 76 | 'Please select which module(s) should be updated', 77 | array_merge(array('None'), array_keys($moduleVersions)), 78 | '0', 79 | true 80 | ); 81 | 82 | $this->tools->nl(); 83 | $this->tools->writeSection('You\'ve selected', implode(', ', $modules)); 84 | 85 | // if not `None` was selected, update modules 86 | if (!in_array('None', $modules) && $modules) $this->upgradeModules($modules, $output); 87 | } 88 | } else { 89 | $this->tools->write('Your modules are up-to-date.'); 90 | } 91 | } 92 | 93 | return static::SUCCESS; 94 | } 95 | 96 | /** 97 | * Upgrade modules 98 | * 99 | * @param array $modules 100 | * @param OutputInterface $output 101 | */ 102 | private function upgradeModules($modules, $output) { 103 | foreach ($modules as $module) { 104 | // check whether module exists/is installed 105 | if (!$this->checkIfModuleExists($module)) { 106 | $this->tools->writeError("Module `$module` does not exist."); 107 | continue; 108 | } 109 | 110 | $moduleVersions = $this->getModuleVersions(true); 111 | // all modules are up-to-date 112 | if ($info = $this->getModuleVersion($module, true)) { 113 | $this->tools->writeBlockBasic("Upgrading `$module` to version {$info['remote']}."); 114 | } else { 115 | $this->tools->write("The module `$module` is up-to-date."); 116 | continue; 117 | } 118 | 119 | // update url available? 120 | if (!$info['download_url']) { 121 | $this->tools->writeError("No download URL specified for module `$module`."); 122 | continue; 123 | } 124 | 125 | // update module 126 | $destinationDir = \ProcessWire\wire('config')->paths->siteModules . $module . '/'; 127 | \ProcessWire\wire('modules')->resetCache(); 128 | parent::setModule($module); 129 | 130 | try { 131 | $this 132 | ->downloadModule($info['download_url'], $module, $output) 133 | ->extractModule($module, $output) 134 | ->cleanUpTmp($module, $output); 135 | } catch (Exception $e) { 136 | $this->tools->writeError("Could not download module `$module`. Please try again later."); 137 | } 138 | 139 | $this->tools->nl(); 140 | $this->tools->writeSuccess("Module `$module` was updated successfully."); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Commands/Page/PageDeleteCommand.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class PageDeleteCommand extends PwConnector { 20 | 21 | /** 22 | * Configures the current command. 23 | */ 24 | public function configure() { 25 | $this 26 | ->setName('page:delete') 27 | ->setDescription('Deletes ProcessWire pages') 28 | ->addArgument('selector', InputArgument::OPTIONAL) 29 | ->addOption('rm', null, InputOption::VALUE_NONE, 'Force deletion, do not move page to trash'); 30 | } 31 | 32 | /** 33 | * @param InputInterface $input 34 | * @param OutputInterface $output 35 | * @return int|null|void 36 | */ 37 | public function execute(InputInterface $input, OutputInterface $output) { 38 | $this->init($input, $output); 39 | $this->tools = new Tools($output); 40 | $this->tools 41 | ->setHelper($this->getHelper('question')) 42 | ->setInput($input) 43 | ->writeBlockCommand($this->getName()); 44 | 45 | $this->pages = \ProcessWire\wire('pages'); 46 | $this->config = \ProcessWire\wire('config'); 47 | $forceDeletion = $input->getOption('rm') === true ? true : false; 48 | 49 | $this->preservedIds = array( 50 | $this->config->adminRootPageID, 51 | $this->config->trashPageID, 52 | $this->config->rootPageID, 53 | $this->config->http404PageID 54 | ); 55 | 56 | $options = array('include' => 'all'); 57 | $limitSelect = array( 58 | "has_parent!={$this->config->adminRootPageID}", 59 | 'id!=' . implode('|', $this->preservedIds), 60 | 'status<' . \processWire\Page::statusTrash 61 | ); 62 | 63 | // ask for selector 64 | $selectorString = $input->getArgument('selector') ? $input->getArgument('selector') : null; 65 | if (!$selectorString) { 66 | $selectorString = $this->tools->ask(null, 'Provide selector or page id'); 67 | } 68 | 69 | foreach (explode(',', $selectorString) as $selector) { 70 | $select = (is_numeric($selector)) ? (int)$selector : $selector; 71 | $pagesToBeDeleted = $this->pages->find($select, $options); 72 | 73 | // no matches? exit 74 | if ($pagesToBeDeleted->count() === 0) { 75 | $this->tools->writeError("No pages were found using selector `{$selector}`."); 76 | exit(1); 77 | } 78 | 79 | $this->deletePages($pagesToBeDeleted, $forceDeletion); 80 | } 81 | 82 | return static::SUCCESS; 83 | } 84 | 85 | /** 86 | * Delete pages 87 | * 88 | * @param \ProcessWire\PagesArray $pages 89 | * @param boolean $forceDeletion 90 | */ 91 | private function deletePages($pagesToBeDeleted, $forceDeletion) { 92 | foreach ($pagesToBeDeleted as $p) { 93 | $this->tools->nl(); 94 | 95 | if ($p instanceof \ProcessWire\NullPage) { 96 | $this->tools->writeError("Page `{$selector}` doesn't exist."); 97 | } else { 98 | $title = $p->get('title|name'); // remember title 99 | 100 | // check whether the page is allowed to be deleted 101 | if (in_array($p->id, $this->preservedIds)) { 102 | $this->tools->writeError("Page `{$title}` may not be deleted."); 103 | continue; 104 | } elseif ($p->parents("id={$this->config->adminRootPageID}")->count()) { 105 | $this->tools->writeError("Page `{$title}` (admin page) may not be deleted."); 106 | continue; 107 | } 108 | 109 | // and delete it / move it to the trash 110 | if ($forceDeletion) { 111 | $this->pages->delete($p, true); 112 | $this->tools->writeSuccess("Page `{$title}` was successfully deleted."); 113 | } else { 114 | $this->pages->trash($p, true); 115 | $this->tools->writeSuccess("Page `{$title}` has been successfully moved to the trash."); 116 | } 117 | } 118 | } 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/Commands/Page/PageEmptyTrashCommand.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class PageEmptyTrashCommand extends PwUserTools { 21 | protected $maxItems = 1000; 22 | 23 | /** 24 | * Configures the current command. 25 | */ 26 | public function configure() { 27 | $this 28 | ->setName('page:emptytrash') 29 | ->setDescription('Empty Trash'); 30 | } 31 | 32 | /** 33 | * @param InputInterface $input 34 | * @param OutputInterface $output 35 | * @return int|null|void 36 | */ 37 | public function execute(InputInterface $input, OutputInterface $output) { 38 | $this->init($input, $output); 39 | $tools = new Tools($output); 40 | $tools 41 | ->setHelper($this->getHelper('question')) 42 | ->setInput($input) 43 | ->writeBlockCommand($this->getName()); 44 | 45 | $pages = \ProcessWire\wire('pages'); 46 | $config = \ProcessWire\wire('config'); 47 | 48 | $trashed = "parent_id={$config->trashPageID},limit={$this->maxItems},"; 49 | $trashed .= "status<" . Page::statusMax . ",include=all"; 50 | 51 | $trashPages = $pages->find($trashed); 52 | 53 | if ($trashPages->getTotal() > 0) { 54 | foreach ($trashPages as $t) $pages->delete($t, true); 55 | $tools->writeSuccess("The trash was successfully cleared, {$trashPages->getTotal()} pages were deleted."); 56 | } else { 57 | $tools->writeInfo("The trash is already empty."); 58 | } 59 | 60 | return static::SUCCESS; 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/Commands/Page/PageListCommand.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class PageListCommand extends PwUserTools { 21 | 22 | /** 23 | * @var Integer 24 | */ 25 | private $indent; 26 | 27 | /** 28 | * @var String 29 | */ 30 | private $select; 31 | 32 | 33 | /** 34 | * Configures the current command. 35 | */ 36 | public function configure() { 37 | $this 38 | ->setName('page:list') 39 | ->setDescription('Lists ProcessWire pages') 40 | ->addOption('start', null, InputOption::VALUE_REQUIRED, 'Start Page') 41 | ->addOption('level', null, InputOption::VALUE_REQUIRED, 'How many levels to show') 42 | ->addOption('all', null, InputOption::VALUE_NONE, 'Get a list of all pages (recursiv) without admin-pages') 43 | ->addOption('trash', null, InputOption::VALUE_NONE, 'Get a list of trashed pages (recursiv) without admin-pages'); 44 | } 45 | 46 | /** 47 | * @param InputInterface $input 48 | * @param OutputInterface $output 49 | * @return int|null|void 50 | */ 51 | public function execute(InputInterface $input, OutputInterface $output) { 52 | $this->init($input, $output); 53 | $this->tools = new Tools($output); 54 | $this->tools->writeBlockCommand($this->getName()); 55 | 56 | $pages = \ProcessWire\wire('pages'); 57 | $this->output = $output; 58 | $this->indent = 0; 59 | 60 | $level = ((int)$input->getOption('level')) ? (int)$input->getOption('level') : 0; 61 | $start = $this->getStartPage($input); 62 | $this->listPages($pages->get($start), $level); 63 | 64 | return static::SUCCESS; 65 | } 66 | 67 | /** 68 | * @param Page $page 69 | * @param int $level 70 | */ 71 | public function listPages($page, $level) { 72 | $indent = 4; 73 | $title = $this->tools->writeInfo($page->title, false) 74 | . $this->tools->writeComment(' { ', false) 75 | . $this->tools->writeMark($page->id, false) 76 | . $this->tools->writeComment(', ', false) 77 | . $this->tools->write($page->template, null, false) 78 | . $this->tools->writeComment(' }', false); 79 | switch ($this->indent) { 80 | case 0: 81 | $out = $this->tools->writeComment('|-- ', false) . $title; 82 | break; 83 | default: 84 | $i = $this->indent - $indent / 2; 85 | $j = $indent / 2 + 1; 86 | $out = '|' . str_pad(' ' . $title, strlen($title) + $j, '-', STR_PAD_LEFT); 87 | $out = '|' . str_pad($out, strlen($out) + $i, ' ', STR_PAD_LEFT); 88 | $out = preg_replace('/(\|)(\s*\|--)?/', $this->tools->writeComment("$1$2", false), $out); 89 | } 90 | 91 | $this->output->writeln($out); 92 | 93 | if ($page->numChildren) { 94 | $this->indent = $this->indent + $indent; 95 | foreach ($page->children($this->select) as $child) { 96 | if ($level === 0 || ($level != 0 && $level >= ($this->indent / $indent))) { 97 | $this->listPages($child, $level); 98 | } 99 | } 100 | $this->indent = $this->indent - $indent; 101 | } 102 | } 103 | 104 | /** 105 | * @param InputInterface $input 106 | * @param $start 107 | */ 108 | private function setSelector($input, $start) { 109 | $config = \ProcessWire\wire('config'); 110 | $inclAll = $input->getOption('all') === true ? true : false; 111 | $inclTrashed = $input->getOption('trash') === true ? true : false; 112 | 113 | if ($inclAll === true && $inclTrashed === true) { 114 | $select = "has_parent!={$config->adminRootPageID},"; 115 | $select .= "id!={$config->adminRootPageID},"; 116 | $select .= "include=all"; 117 | } elseif ($inclAll === true) { 118 | $select = "has_parent!={$config->adminRootPageID},"; 119 | $select .= "id!={$config->adminRootPageID}|{$config->trashPageID},"; 120 | $select .= "status<" . Page::statusTrash . ",include=all"; 121 | } elseif ($inclTrashed === true) { 122 | $select = "include=all"; 123 | $start = $config->trashPageID; 124 | } else { 125 | $select = ''; 126 | } 127 | 128 | $this->select = $select; 129 | return $start; 130 | } 131 | 132 | /** 133 | * @param InputInterface $input 134 | */ 135 | private function getStartPage($input) { 136 | $start = '/'; 137 | // start page submitted and existing? 138 | if ($input->getOption('start')) { 139 | $startPage = $input->getOption('start'); 140 | $startPage = (is_numeric($startPage)) ? (int)$startPage : "/{$startPage}/"; 141 | 142 | if (!\ProcessWire\wire('pages')->get($startPage) instanceof \ProcessWire\NullPage) { 143 | $start = $startPage; 144 | } else { 145 | $this->tools->writeError("Startpage `{$startPage}` could not be found, using root page instead."); 146 | $this->tools->nl(); 147 | } 148 | } 149 | 150 | return $this->setSelector($input, $start); 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /src/Commands/Role/RoleCreateCommand.php: -------------------------------------------------------------------------------- 1 | setName('role:create') 25 | ->setDescription('Creates a ProcessWire role') 26 | ->addArgument('name', InputArgument::OPTIONAL, 'comma-separated list'); 27 | } 28 | 29 | /** 30 | * @param InputInterface $input 31 | * @param OutputInterface $output 32 | * @return int|null|void 33 | */ 34 | public function execute(InputInterface $input, OutputInterface $output) { 35 | $this->init($input, $output); 36 | $tools = new Tools($output); 37 | $tools 38 | ->setInput($input) 39 | ->setHelper($this->getHelper('question')); 40 | $tools->writeBlockCommand($this->getName()); 41 | 42 | $names = $tools->ask($input->getArgument('name'), 'Enter roles to add, comma-separated'); 43 | foreach (explode(',', preg_replace('/\s+/', '', $names)) as $name) { 44 | if (!\ProcessWire\wire('roles')->get($name) instanceof \ProcessWire\NullPage) { 45 | $tools->writeError("Role '{$name}' already exists."); 46 | } else { 47 | \ProcessWire\wire('roles')->add($name); 48 | $tools->writeSuccess("Role '{$name}' created successfully."); 49 | } 50 | } 51 | 52 | return static::SUCCESS; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/Commands/Role/RoleDeleteCommand.php: -------------------------------------------------------------------------------- 1 | setName('role:delete') 25 | ->setDescription('Deletes ProcessWire role(s)') 26 | ->addArgument('roles', InputArgument::OPTIONAL, 'comma-separated list'); 27 | } 28 | 29 | /** 30 | * @param InputInterface $input 31 | * @param OutputInterface $output 32 | * @return int|null|void 33 | */ 34 | public function execute(InputInterface $input, OutputInterface $output) { 35 | $this->init($input, $output); 36 | $roles = \ProcessWire\wire('roles'); 37 | $tools = new Tools($output); 38 | $tools 39 | ->setInput($input) 40 | ->setHelper($this->getHelper('question')); 41 | $tools->writeBlockCommand($this->getName()); 42 | 43 | $options = $this->getAvailableRoles($input->getArgument('roles')); 44 | 45 | // superuser and guest may not be deleted 46 | unset($options[array_search('guest', $options)]); 47 | unset($options[array_search('superuser', $options)]); 48 | 49 | if (count($options) === 0) { 50 | $tools->writeError('There are no roles which can be deleted.'); 51 | exit(1); 52 | } 53 | 54 | $names = $tools->askChoice( 55 | $input->getArgument('roles'), 56 | 'Which roles should be deleted', 57 | $options, 58 | '0', 59 | true 60 | ); 61 | 62 | if (!is_array($names)) explode(',', preg_replace('/\s+/', '', $names)); 63 | 64 | foreach ($names as $name) { 65 | $tools->nl(); 66 | if ($roles->get($name) instanceof \ProcessWire\NullPage) { 67 | $tools->writeError("Role '{$name}' does not exist."); 68 | exit(1); 69 | } 70 | 71 | $roles->delete($roles->get($name)); 72 | $tools->writeSuccess("Role '{$name}' has been deleted successfully."); 73 | } 74 | 75 | return static::SUCCESS; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/Commands/Role/RoleListCommand.php: -------------------------------------------------------------------------------- 1 | setName('role:list') 25 | ->setDescription('Lists ProcessWire role(s)'); 26 | } 27 | 28 | /** 29 | * @param InputInterface $input 30 | * @param OutputInterface $output 31 | * @return int|null|void 32 | */ 33 | public function execute(InputInterface $input, OutputInterface $output) { 34 | $this->init($input, $output); 35 | $tools = new Tools($output); 36 | $tools->writeBlockCommand($this->getName()); 37 | 38 | foreach (\ProcessWire\wire('roles') as $role) { 39 | $tools->writeInfo(" - {$role->name}"); 40 | } 41 | 42 | return static::SUCCESS; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Commands/Template/TemplateCreateCommand.php: -------------------------------------------------------------------------------- 1 | setName('template:create') 29 | ->setDescription('Creates a ProcessWire template') 30 | ->addArgument('name', InputArgument::OPTIONAL, 'Name of template') 31 | ->addOption('fields', null, InputOption::VALUE_REQUIRED, 32 | 'Attach existing fields to template, comma separated') 33 | ->addOption('nofile', null, InputOption::VALUE_NONE, 'Prevents template file creation'); 34 | } 35 | 36 | /** 37 | * @param InputInterface $input 38 | * @param OutputInterface $output 39 | * @return int|null|void 40 | */ 41 | public function execute(InputInterface $input, OutputInterface $output) { 42 | $this->init($input, $output); 43 | $this->tools = new Tools($output); 44 | $this->tools 45 | ->setHelper($this->getHelper('question')) 46 | ->setInput($input); 47 | 48 | $this->tools->writeBlockCommand($this->getName()); 49 | 50 | $name = ''; 51 | while (!$name) { 52 | $name = $this->tools->ask($input->getArgument('name'), 'Name for new template'); 53 | } 54 | 55 | $availableFields = array(); 56 | foreach (\ProcessWire\wire('fields') as $field) { 57 | if ($field->name !== 'title') $availableFields[] = $field->name; 58 | } 59 | 60 | $fields = $this->tools->askChoice( 61 | $input->getOption('fields'), 62 | "Select fields which should be assigned to template $name:", 63 | array_merge(array('none'), $availableFields), 64 | 0, 65 | true 66 | ); 67 | 68 | if (\ProcessWire\wire('templates')->get($name)) { 69 | $this->tools->writeError("Template '{$name}' already exists!"); 70 | exit(1); 71 | } 72 | 73 | $fieldgroup = new Fieldgroup(); 74 | $fieldgroup->name = $name; 75 | $fieldgroup->add('title'); 76 | 77 | $addFields = !is_array($fields) ? explode(',', $fields) : $fields; 78 | foreach ($addFields as $field) { 79 | if ($field === 'none') continue; 80 | $this->checkIfFieldExists($field, $output); 81 | $fieldgroup->add($field); 82 | } 83 | 84 | $fieldgroup->save(); 85 | 86 | $template = new Template(); 87 | $template->name = $name; 88 | $template->fieldgroup = $fieldgroup; 89 | $template->save(); 90 | 91 | if (!$input->getOption('nofile')) $this->createTemplateFile($name); 92 | 93 | $this->tools->nl(); 94 | $this->tools->writeSuccess("Template '{$name}' created successfully!"); 95 | 96 | return static::SUCCESS; 97 | } 98 | 99 | /** 100 | * @param $name 101 | */ 102 | private function createTemplateFile($name) { 103 | if ($templateFile = fopen('site/templates/' . $name . '.php', 'w')) { 104 | $content = "get($field)) { 118 | $this->tools->writeComment("Field '{$field}' does not exist!"); 119 | $this->tools->nl(); 120 | 121 | return false; 122 | } 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/Commands/Template/TemplateDeleteCommand.php: -------------------------------------------------------------------------------- 1 | setName('template:delete') 27 | ->setDescription('Deletes ProcessWire template(s)') 28 | ->addArgument('name', InputArgument::OPTIONAL, 'Name of template(s)') 29 | ->addOption('nofile', null, InputOption::VALUE_NONE, 'Prevents template file deletion'); 30 | } 31 | 32 | /** 33 | * @param InputInterface $input 34 | * @param OutputInterface $output 35 | * @return int|null|void 36 | */ 37 | public function execute(InputInterface $input, OutputInterface $output) { 38 | $this->init($input, $output); 39 | $tools = new Tools($output); 40 | $tools 41 | ->setHelper($this->getHelper('question')) 42 | ->setInput($input) 43 | ->writeBlockCommand($this->getName()); 44 | 45 | // ask which template should be deleted 46 | $availableTemplates = $this->getAvailableTemplates(); 47 | $tmplsString = $input->getArgument('name'); 48 | $names = $tmplsString ? explode(',', $tmplsString) : null; 49 | $names = $tools->askChoice($names, 'Select all templates which should be deleted', $availableTemplates, 0, true); 50 | $tools->nl(); 51 | 52 | $templates = \ProcessWire\wire('templates'); 53 | $fieldgroups = \ProcessWire\wire('fieldgroups'); 54 | 55 | foreach ($names as $name) { 56 | $template = $templates->get($name); 57 | if ($template->id) { 58 | // try to delete depending file? 59 | if (!$input->getOption('nofile') && file_exists($template->filename)) { 60 | unlink($template->filename); 61 | } 62 | 63 | $template->flags = Template::flagSystemOverride; 64 | $template->flags = 0; // all flags now removed, can be deleted 65 | $templates->delete($template); 66 | 67 | // delete depending fieldgroups 68 | $fg = $fieldgroups->get($name); 69 | if ($fg->id) $fieldgroups->delete($fg); 70 | $tools->writeSuccess("Template '{$name}' deleted successfully."); 71 | } else { 72 | $tools->writeError("Template '{$name}' doesn't exist."); 73 | } 74 | } 75 | 76 | return static::SUCCESS; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/Commands/Template/TemplateFieldsCommand.php: -------------------------------------------------------------------------------- 1 | setName('template:fields') 27 | ->setDescription('Assign given fields to a given template') 28 | ->addArgument('template', InputArgument::OPTIONAL, 'Name of the template') 29 | ->addOption('fields', null, InputOption::VALUE_REQUIRED, 'Supply fields to assign to template'); 30 | } 31 | 32 | /** 33 | * @param InputInterface $input 34 | * @param OutputInterface $output 35 | * @return int|null|void 36 | */ 37 | protected function execute(InputInterface $input, OutputInterface $output) { 38 | $this->init($input, $output); 39 | $this->errorCount = 0; 40 | $this->tools = new Tools($output); 41 | $this->tools 42 | ->setHelper($this->getHelper('question')) 43 | ->setInput($input); 44 | 45 | $this->tools->writeBlockCommand($this->getName()); 46 | 47 | // check if the template exists 48 | $availableTemplates = $this->getAvailableTemplates(); 49 | $tmpl = $input->getArgument('template'); 50 | if ($tmpl && !in_array($tmpl, $availableTemplates)) { 51 | $tools->writeError("Template `$tmpl` does not exist."); 52 | $tools->nl(); 53 | $tmpl = null; 54 | } 55 | 56 | // ask for template 57 | $template = $this->tools->askChoice($tmpl, 'Select template', $availableTemplates, 0); 58 | $this->tools->nl(); 59 | 60 | // get the fields 61 | $availableFields = array(); 62 | foreach (\ProcessWire\wire('fields') as $field) { 63 | $availableFields[] = $field->name; 64 | } 65 | 66 | $fields = $input->getOption('fields') ? explode(",", $input->getOption('fields')) : ''; 67 | $fields = $this->tools->askChoice($fields, 'Select fields', $availableFields, 0, true); 68 | $this->assignFieldsToTemplate($fields, $template, $output); 69 | 70 | $this->tools->nl(); 71 | 72 | if ($this->errorCount === count($fields)) { 73 | $this->tools->writeError('Field(s) could not be assigned.'); 74 | } elseif ($this->errorCount > 0) { 75 | $this->tools->writeSuccess("Field(s) added to '{$template}' successfully except '{$this->errorCount}'."); 76 | } else { 77 | $this->tools->writeSuccess("Field(s) added to '{$template}' successfully."); 78 | } 79 | 80 | return static::SUCCESS; 81 | } 82 | 83 | /** 84 | * Assign fields to template 85 | * 86 | * @param $fields 87 | * @param $template 88 | * @param $output 89 | */ 90 | private function assignFieldsToTemplate($fields, $template, $output) { 91 | $pwTemplate = \ProcessWire\wire('templates')->get($template); 92 | foreach ($fields as $field) { 93 | if ($this->checkIfFieldExists($field)) { 94 | $pwTemplate->fields->add($field); 95 | $pwTemplate->fields->save(); 96 | } 97 | } 98 | 99 | return $template; 100 | } 101 | 102 | /** 103 | * Check if field exists 104 | * 105 | * @param $field 106 | * @param $output 107 | * @return bool 108 | */ 109 | private function checkIfFieldExists($field) { 110 | if (!\ProcessWire\wire('fields')->get($field)) { 111 | $this->tools->writeError("- Field '{$field}' does not exist!"); 112 | $doesNotExist = false; 113 | $this->errorCount++; 114 | } 115 | 116 | return isset($doesNotExist) ? false : true; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Commands/Template/TemplateInfoCommand.php: -------------------------------------------------------------------------------- 1 | setName('template:info') 27 | ->setDescription('Displays detailed information about a specific template') 28 | ->addArgument('template', InputArgument::OPTIONAL, 'Template name.'); 29 | } 30 | 31 | /** 32 | * @param InputInterface $input 33 | * @param OutputInterface $output 34 | * @return int|null|void 35 | */ 36 | public function execute(InputInterface $input, OutputInterface $output) { 37 | $this->init($input, $output); 38 | $tables = new Tables($output); 39 | 40 | $tools = new Tools($output); 41 | $tools 42 | ->setInput($input) 43 | ->setHelper($this->getHelper('question')) 44 | ->writeBlockCommand($this->getName()); 45 | 46 | $templates = \ProcessWire\wire('templates'); 47 | $inputTemplate = $tools->ask($input->getArgument('template'), 'Template name', null, false, null, 'required'); 48 | $template = $templates->get($inputTemplate); 49 | 50 | $tableInfo = $this->getInfoTable($template, $tables); 51 | $tableFields = $this->getFieldsTable($template, $tables); 52 | $tables->renderTables($tableInfo); 53 | $tables->renderTables($tableFields); 54 | 55 | return static::SUCCESS; 56 | } 57 | 58 | /** 59 | * get template info data 60 | * starting with some basic information 61 | * 62 | * @param Template $template 63 | * @param Tables $tables 64 | * @return array 65 | */ 66 | private function getInfoTable($template, $tables) { 67 | $pages = \ProcessWire\wire('pages'); 68 | $headers = array('Property', 'Value'); 69 | $content = array( 70 | array('ID', $template->id), 71 | array('Tags', $template->tags), 72 | array('File Name', $template->filename), 73 | array('Cache Time', $template->cacheTime), 74 | array('Number of fields', $template->fields->count), 75 | array('Number of pages using this template', $pages->count("template=$template")) 76 | ); 77 | 78 | return array($tables->buildTable($content, $headers)); 79 | } 80 | 81 | /** 82 | * get template fields 83 | * 84 | * @param Template $template 85 | * @param Tables $tables 86 | * @return array 87 | */ 88 | private function getFieldsTable($template, $tables) { 89 | $headers = array('Field', 'Label', 'Type'); 90 | $content = array(); 91 | 92 | foreach ($template->fields as $field) { 93 | $content[] = array( 94 | $field->name, $field->label, str_replace('Fieldtype', '', $field->type) 95 | ); 96 | } 97 | 98 | return array($tables->buildTable($content, $headers)); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/Commands/Template/TemplateListCommand.php: -------------------------------------------------------------------------------- 1 | setName('template:list') 28 | ->setDescription('Lists ProcessWire templates') 29 | ->addOption('advanced', null, InputOption::VALUE_NONE, 'Show system templates. By default, system/internal templates are not shown.'); 30 | } 31 | 32 | /** 33 | * @param InputInterface $input 34 | * @param OutputInterface $output 35 | * @return int|null|void 36 | */ 37 | public function execute(InputInterface $input, OutputInterface $output) { 38 | $this->init($input, $output); 39 | $tools = new Tools($output); 40 | $tables = new Tables($output); 41 | $advanced = $input->getOption('advanced') ? true : false; 42 | 43 | $content = $this->getTemplateData($advanced); 44 | $headers = array('Template', 'Fields', 'Pages', 'Modified', 'Access'); 45 | $templateTables = array($tables->buildTable($content, $headers)); 46 | 47 | $tools->writeBlockCommand($this->getName()); 48 | $tables->renderTables($templateTables); 49 | 50 | return static::SUCCESS; 51 | } 52 | 53 | /** 54 | * get templates data 55 | * 56 | * @param boolean $advanced 57 | * @return array 58 | */ 59 | private function getTemplateData($advanced) { 60 | $content = array(); 61 | $advanced = \ProcessWire\wire('config')->advanced || $advanced; 62 | foreach (\ProcessWire\wire('templates') as $t) { 63 | if (!$advanced && ($t->flags & Template::flagSystem)) continue; 64 | 65 | $content[] = array( 66 | $t->name, 67 | count($t->fieldgroup), 68 | $t->getNumPages(), 69 | \ProcessWire\wireRelativeTimeStr($t->modified), 70 | $t->flags & Template::flagSystem ? '✖' : '' 71 | ); 72 | } 73 | 74 | return $content; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/Commands/Template/TemplateTagCommand.php: -------------------------------------------------------------------------------- 1 | setName('template:tag') 26 | ->setDescription('Tags templates') 27 | ->addArgument('template', InputArgument::OPTIONAL, 'Comma separated list.') 28 | ->addOption('tag', null, InputOption::VALUE_REQUIRED, 'Tag name'); 29 | } 30 | 31 | /** 32 | * @param InputInterface $input 33 | * @param OutputInterface $output 34 | * @return int|null|void 35 | */ 36 | protected function execute(InputInterface $input, OutputInterface $output) { 37 | $this->init($input, $output); 38 | $tools = new Tools($output); 39 | $tools 40 | ->setInput($input) 41 | ->setHelper($this->getHelper('question')) 42 | ->writeBlockCommand($this->getName()); 43 | 44 | $templates = \ProcessWire\wire('templates'); 45 | 46 | $inputTemplates = $tools->ask($input->getArgument('template'), 'Template name(s), comma-separated', null, false, null, 'required'); 47 | $tag = $tools->ask($input->getOption('tag'), 'Tag name', null, false, null, 'required'); 48 | 49 | foreach (explode(',', $inputTemplates) as $template) { 50 | $templateToTag = $templates->get($template); 51 | $tools->nl(); 52 | 53 | if (is_null($templateToTag)) { 54 | $tools->writeError(" > Template '{$template}' does not exist."); 55 | continue; 56 | } 57 | 58 | try { 59 | $templateToTag->tags = $tag; 60 | $templateToTag->save(); 61 | $tools->writeSuccess(" > Template '{$template}' edited successfully."); 62 | } catch (\WireException $e) { 63 | $tools->writeError(" > {$e->getMessage()}"); 64 | } 65 | } 66 | 67 | return static::SUCCESS; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Commands/User/UserCreateCommand.php: -------------------------------------------------------------------------------- 1 | setName('user:create') 28 | ->setDescription('Creates a ProcessWire user') 29 | ->addArgument('name', InputArgument::OPTIONAL, 'Name of user.') 30 | ->addOption('email', null, InputOption::VALUE_REQUIRED, 'Supply an email address') 31 | ->addOption('password', null, InputOption::VALUE_REQUIRED, 'Supply a password') 32 | ->addOption('roles', null, InputOption::VALUE_REQUIRED, 'Attach existing roles to user, comma separated'); 33 | } 34 | 35 | /** 36 | * @param InputInterface $input 37 | * @param OutputInterface $output 38 | * @return int|null|void 39 | */ 40 | public function execute(InputInterface $input, OutputInterface $output) { 41 | $this->init($input, $output); 42 | $tools = new Tools($output); 43 | $tools 44 | ->setHelper($this->getHelper('question')) 45 | ->setInput($input) 46 | ->writeBlockCommand($this->getName()); 47 | 48 | $name = $tools->ask($input->getArgument('name'), 'Username', null, false, null, 'required'); 49 | $email = $tools->ask($input->getOption('email'), 'E-Mail-Address', null, false, null, 'email'); 50 | $pass = $tools->ask($input->getOption('password'), 'Password', $tools->generatePassword(), true); 51 | 52 | while (!\ProcessWire\wire("pages")->get("name={$name}") instanceof \ProcessWire\NullPage) { 53 | $tools->writeError("User '{$name}' already exists, please choose another one"); 54 | $name = $tools->ask($input->getArgument('name'), 'Username'); 55 | } 56 | 57 | $user = $this->createUser($email, $name, $this->userContainer, $pass); 58 | $user->save(); 59 | 60 | $options = $this->getAvailableRoles($input->getOption('roles')); 61 | 62 | $roles = $tools->askChoice( 63 | $input->getOption('roles'), 64 | 'Which roles should be attached', 65 | $options, 66 | array_search('guest', $options), 67 | true 68 | ); 69 | 70 | $tools->nl(); 71 | 72 | if ($roles) $this->attachRolesToUser($name, $roles, $output); 73 | 74 | if ($pass) { 75 | $tools->writeInfo("User '{$name}' created successfully!"); 76 | } else { 77 | $tools->writeInfo("User '{$name}' created successfully! Please do not forget to set a password."); 78 | } 79 | 80 | return static::SUCCESS; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/Commands/User/UserDeleteCommand.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | 20 | class UserDeleteCommand extends PwUserTools { 21 | 22 | /** 23 | * Configures the current command. 24 | */ 25 | public function configure() { 26 | $this 27 | ->setName('user:delete') 28 | ->setDescription('Deletes ProcessWire users') 29 | ->addArgument('name', InputArgument::OPTIONAL, 'Name of user') 30 | ->addOption('role', null, InputOption::VALUE_REQUIRED, 'Delete user(s) by role'); 31 | } 32 | 33 | /** 34 | * @param InputInterface $input 35 | * @param OutputInterface $output 36 | * @return int|null|void 37 | */ 38 | public function execute(InputInterface $input, OutputInterface $output) { 39 | $this->init($input, $output); 40 | $tools = new Tools($output); 41 | $tools 42 | ->setInput($input) 43 | ->setHelper($this->getHelper('question')); 44 | 45 | // if argument role is provided, delete all users with specific role 46 | if ($role = $input->getOption('role')) { 47 | $users = \ProcessWire\wire('users')->find("roles=$role"); 48 | 49 | foreach ($users as $user) { 50 | \ProcessWire\wire('users')->delete($user); 51 | } 52 | 53 | $output->writeln("Deleted {$users->count()} users successfully!"); 54 | } else { 55 | // check name 56 | $availableUsers = parent::getAvailableUsers(true); 57 | $urs = $input->getArgument('name'); 58 | $users = $urs ? explode(',', $urs) : null; 59 | 60 | $users = $tools->askChoice($users, 'Select all users which should be deleted', $availableUsers, 0, true); 61 | 62 | $tools->nl(); 63 | foreach ($users as $name) { 64 | if (\ProcessWire\wire('users')->get($name) instanceof \ProcessWire\NullPage) { 65 | $tools->writeError("User '{$name}' doesn't exists."); 66 | } else { 67 | $user = \ProcessWire\wire('users')->get($name); 68 | \ProcessWire\wire('users')->delete($user); 69 | $tools->writeSuccess("User '{$name}' deleted successfully."); 70 | } 71 | } 72 | } 73 | 74 | return static::SUCCESS; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Commands/User/UserListCommand.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | 21 | class UserListCommand extends PwUserTools { 22 | 23 | /** 24 | * Configures the current command. 25 | */ 26 | public function configure() { 27 | $this 28 | ->setName('user:list') 29 | ->setDescription('Lists ProcessWire users') 30 | ->addOption('role', null, InputOption::VALUE_REQUIRED, 'Find user by role'); 31 | } 32 | 33 | /** 34 | * @param InputInterface $input 35 | * @param OutputInterface $output 36 | * @return int|null|void 37 | */ 38 | public function execute(InputInterface $input, OutputInterface $output) { 39 | $this->init($input, $output); 40 | $users = $this->getUsers($input); 41 | $tools = new Tools($output); 42 | $tables = new Tables($output); 43 | $tools->writeBlockCommand($this->getName()); 44 | 45 | if ($users->getTotal() > 0) { 46 | $content = $this->getUserData($users); 47 | $headers = array('Username', 'E-Mail', 'Superuser', 'Roles'); 48 | 49 | $userTables = array($tables->buildTable($content, $headers)); 50 | $tables->renderTables($userTables, false); 51 | } 52 | 53 | $tools->writeCount($users->getTotal()); 54 | 55 | return static::SUCCESS; 56 | } 57 | 58 | /** 59 | * get users 60 | * 61 | * @param InputInterface $input 62 | */ 63 | private function getUsers($input) { 64 | $role = $input->getOption('role'); 65 | 66 | if ($role) { 67 | $users = \ProcessWire\wire('users')->find('roles=' . $input->getOption('role'))->sort('name'); 68 | } else { 69 | $users = \ProcessWire\wire('users')->find('start=0')->sort('name'); 70 | } 71 | 72 | return $users; 73 | } 74 | 75 | /** 76 | * get user data 77 | * 78 | * @param $users 79 | * @return array 80 | */ 81 | private function getUserData($users) { 82 | $content = array(); 83 | foreach ($users as $user) { 84 | $roles = array(); 85 | foreach ($user->roles as $role) { 86 | $roles[] = $role->name; 87 | } 88 | 89 | $content[] = array( 90 | $user->name, 91 | $user->email, 92 | $user->isSuperuser() ? '✔' : '', 93 | implode(', ', $roles) 94 | ); 95 | } 96 | 97 | return $content; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/Commands/User/UserUpdateCommand.php: -------------------------------------------------------------------------------- 1 | setName('user:update') 29 | ->setDescription('Updates a ProcessWire user') 30 | ->addArgument('name', InputArgument::OPTIONAL, 'Name of user') 31 | ->addOption('newname', null, InputOption::VALUE_REQUIRED, 'Supply an user name') 32 | ->addOption('email', null, InputOption::VALUE_REQUIRED, 'Supply an email address') 33 | ->addOption('password', null, InputOption::VALUE_REQUIRED, 'Supply a password') 34 | ->addOption('roles', null, InputOption::VALUE_REQUIRED, 'Attach existing roles to user, comma separated'); 35 | } 36 | 37 | /** 38 | * @param InputInterface $input 39 | * @param OutputInterface $output 40 | * @return int|null|void 41 | */ 42 | public function execute(InputInterface $input, OutputInterface $output) { 43 | $this->init($input, $output); 44 | $tools = new Tools($output); 45 | $tools 46 | ->setInput($input) 47 | ->setHelper($this->getHelper('question')); 48 | $tools->writeBlockCommand($this->getName()); 49 | 50 | // ask for username 51 | $name = $tools->askChoice( 52 | $input->getArgument('name'), 53 | 'Which user should be updated?', 54 | $this->getAvailableUsers(), 55 | 0 56 | ); 57 | 58 | $tools->nl(); 59 | 60 | // check if user exists 61 | $user = \ProcessWire\wire('pages')->get("name={$name}"); 62 | if ($user instanceof \ProcessWire\NullPage) { 63 | $tools->writeError("User '{$name}' does not exist."); 64 | exit(1); 65 | } 66 | 67 | // update roles, pass, name and email 68 | $roles = $input->getOption('roles') ? explode(",", $input->getOption('roles')) : null; 69 | $pass = $input->getOption('password'); 70 | $newname = $input->getOption('newname'); 71 | $email = $input->getOption('email'); 72 | 73 | if (!$roles && !$pass && !$newname && !$email) { 74 | $newname = $tools->ask('', 'New name', $name); 75 | $email = $tools->ask('', 'E-Mail-Address', $user->email, false, null, 'email'); 76 | 77 | $pass = $tools->ask($input->getOption('password'), 'Password', self::PASS_MASK, true); 78 | if ($pass === self::PASS_MASK) $pass = null; 79 | } 80 | 81 | $user = $this->updateUser($name, $pass, $email, $newname); 82 | $user->save(); 83 | 84 | // ROLES 85 | // get available roles 86 | $availableRoles = $this->getAvailableRoles($input->getOption('roles')); 87 | 88 | // get current roles 89 | $currentRoles = array(); 90 | foreach ($user->roles as $role) { 91 | $currentRoles[array_search($role->name, $availableRoles)] = $role->name; 92 | } 93 | 94 | // askQuestion 95 | $roles = $tools->askChoice( 96 | '', 97 | 'Which roles should be attached', 98 | $availableRoles, 99 | implode(',', array_keys($currentRoles)), 100 | true 101 | ); 102 | 103 | if ($roles) $this->attachRolesToUser($name, $roles, $output); 104 | 105 | $tools->nl(); 106 | $tools->writeSuccess("User '{$name}' updated successfully."); 107 | 108 | return static::SUCCESS; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/Helpers/ProcessDiagnostics/DiagnoseImagehandling.php: -------------------------------------------------------------------------------- 1 | 'GD library', 19 | 'value' => $ver, 20 | 'status' => ProcessDiagnostics::$fail, 21 | 'action' => 'There seems to be no GD-library installed!' 22 | ); 23 | } else { 24 | $gd = gd_info(); 25 | $ver = isset($gd['GD Version']) ? $gd['GD Version'] : 'Version-Info not available'; 26 | $jpg = isset($gd['JPEG Support']) ? $gd['JPEG Support'] : false; 27 | $png = isset($gd['PNG Support']) ? $gd['PNG Support'] : false; 28 | $gif = isset($gd['GIF Read Support']) && isset($gd['GIF Create Support']) ? $gd['GIF Create Support'] : false; 29 | $freetype = isset($gd['FreeType Support']) ? $gd['FreeType Support'] : false; 30 | 31 | // GD version 32 | $results[] = array( 33 | 'title' => 'GD library', 34 | 'value' => ProcessDiagnostics::initCap($ver), 35 | 'status' => ProcessDiagnostics::$ok, 36 | 'action' => '' 37 | ); 38 | 39 | // PHP with GD bug ? 40 | if((version_compare(PHP_VERSION, '5.5.8', '>') && version_compare(PHP_VERSION, '5.5.11', '<'))) { 41 | if(version_compare($this->config->version, '2.4.1', '<')) { 42 | $results[] = array( 43 | 'title' => 'GD library Bug', 44 | 'value' => 'Possible bug in GD-Version', 45 | 'status' => ProcessDiagnostics::$warn, // @steve: or better use ProcessDiagnostics::fail ? 46 | 'action' => 'Bundled GD libraries in PHP versions 5.5.9 and 5.5.10 are known as buggy. You should update A) your PHP version to 5.5.11 or newer, or B) the ProcessWire version to 2.4.2 or newer' 47 | ); 48 | } 49 | } 50 | 51 | // GD supported types 52 | foreach(array('JPG', 'PNG', 'GIF', 'FreeType') as $v) { 53 | $name = sprintf('GD %s Support', $v); 54 | $v = strtolower($v); 55 | $value = $$v ? 'Supported' : 'Not supported'; 56 | $status = $$v ? ProcessDiagnostics::$ok : ProcessDiagnostics::$fail; 57 | $results[] = array( 58 | 'title' => $name, 59 | 'value' => $value, 60 | 'status' => $status, 61 | 'action' => '' 62 | ); 63 | } 64 | } 65 | 66 | 67 | // check if we can read exif data 68 | 69 | $res = function_exists('exif_read_data'); 70 | $action = $res ? '' : "Not needed for PW core, might be needed by third party modules."; 71 | $results[] = array( 72 | 'title' => 'Exif read data', 73 | 'value' => $res ? 'Available' : 'Not available', 74 | 'status' => $res ? ProcessDiagnostics::$ok : ProcessDiagnostics::$warn, 75 | 'action' => $action 76 | ); 77 | 78 | 79 | // check if Imagick is supported 80 | 81 | if(!class_exists('Imagick')) { 82 | $results[] = array( 83 | 'title' => 'Imagick Extension', 84 | 'value' => 'Not available', 85 | 'status' => ProcessDiagnostics::$warn, 86 | 'action' => 'Not needed for PW core, might be needed by third party modules.' 87 | ); 88 | } else { 89 | if(ProcessDiagnostics::isDisabled('phpinfo')) { 90 | $results[] = array( 91 | 'title' => 'Imagick Extension', 92 | 'value' => 'Available', 93 | 'status' => ProcessDiagnostics::$warn, 94 | 'action' => 'Odd, retrieving phpinfo on your server is disabled! We cannot get further informations here.' 95 | ); 96 | } else { 97 | $results[] = array( 98 | 'title' => 'Imagick Extension', 99 | 'value' => 'Available', 100 | 'status' => ProcessDiagnostics::$ok, 101 | 'action' => '' 102 | ); 103 | 104 | ob_start(); 105 | phpinfo(); 106 | $buffer = ob_get_clean(); 107 | $pattern = '/>imagick<.*?.*?<\/table>)/msi'; 108 | preg_match($pattern, $buffer, $matches); 109 | 110 | if (isset($matches[1])) { 111 | $buf = trim(str_replace('', '', $matches[1])); 112 | $a = explode("\n", strip_tags(str_replace(array("\r\n", "\r", '### $k, 121 | 'value' => $v, 122 | 'status' => ProcessDiagnostics::$ok, 123 | 'action' => '' 124 | ); 125 | } 126 | } 127 | } 128 | } 129 | 130 | return $results; 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /src/Helpers/ProcessDiagnostics/DiagnosePhp.php: -------------------------------------------------------------------------------- 1 | 'Version', 31 | 'value' => PHP_VERSION, 32 | 'status' => $status, 33 | 'action' => $action 34 | ); 35 | 36 | ob_start(); 37 | phpinfo(INFO_GENERAL); 38 | $buffer = str_replace("\r\n", "\n", ob_get_contents()); 39 | ob_end_clean(); 40 | 41 | $pattern = preg_match('##msi', 42 | $buffer) === 1 ? '#>Server API.*?(.*?)#msi' : '#\nServer API.*?=>(.*?)\n#msi'; 43 | $api = preg_match($pattern, $buffer, $matches) === 1 ? trim($matches[1]) : null; 44 | 45 | $pattern = preg_match('##msi', 46 | $buffer) === 1 ? '#>System.*?(.*?)#msi' : '#\nSystem.*?=>(.*?)\n#msi'; 47 | $sys = preg_match($pattern, $buffer, $matches) === 1 ? trim($matches[1]) : null; 48 | 49 | // build results array PHP Handler 50 | $results[] = array( 51 | 'title' => 'Handler', 52 | 'value' => $api, 53 | 'status' => ProcessDiagnostics::$ok, 54 | 'action' => '' 55 | ); 56 | 57 | // build results array PHP system info 58 | $results[] = array( 59 | 'title' => 'System Information', 60 | 'value' => $sys, 61 | 'status' => ProcessDiagnostics::$ok, 62 | 'action' => '' 63 | ); 64 | 65 | 66 | // build results array PHP Timezone 67 | $results[] = array( 68 | 'title' => 'Timezone', 69 | 'value' => ini_get('date.timezone'), 70 | 'status' => ProcessDiagnostics::$ok, 71 | 'action' => '' 72 | ); 73 | 74 | 75 | // build results array PHP Max Memory 76 | $mem = trim(ini_get('memory_limit')); 77 | $results[] = array( 78 | 'title' => 'Max Memory', 79 | 'value' => $mem, 80 | 'status' => ProcessDiagnostics::$ok, 81 | 'action' => '' 82 | ); 83 | 84 | 85 | // build results array PHP Max Execution Time 86 | $max_execution_time = trim(ini_get('max_execution_time')); 87 | $can_change = set_time_limit($max_execution_time); 88 | $can_change = ($can_change) ? 89 | "This can be extended by calling set_time_limit()." : 90 | "Fixed. Calling set_time_limit() has no effect."; 91 | $results[] = array( 92 | 'title' => 'Max execution time', 93 | 'value' => $max_execution_time, 94 | 'status' => ProcessDiagnostics::$ok, 95 | 'action' => $can_change 96 | ); 97 | 98 | 99 | // POST & Upload related :: Max Input Time 100 | $max_input_time = trim(ini_get('max_input_time')); 101 | $results[] = array( 102 | 'title' => 'Maximum input time', 103 | 'value' => $max_input_time, 104 | 'status' => ProcessDiagnostics::$ok, 105 | 'action' => '' 106 | ); 107 | 108 | // POST & Upload related :: Upload Max Filesize 109 | $upload_max_filesize = trim(ini_get('upload_max_filesize')); 110 | $results[] = array( 111 | 'title' => 'Upload Max Filesize', 112 | 'value' => $upload_max_filesize, 113 | 'status' => ProcessDiagnostics::$ok, 114 | 'action' => '' 115 | ); 116 | 117 | // POST & Upload related :: Post Max Size 118 | $post_max_size = trim(ini_get('post_max_size')); 119 | $results[] = array( 120 | 'title' => 'Post Max Size', 121 | 'value' => $post_max_size, 122 | 'status' => ProcessDiagnostics::$ok, 123 | 'action' => '' 124 | ); 125 | 126 | // POST & Upload related :: Max Input Vars 127 | $max_input_vars = trim(ini_get('max_input_vars')); 128 | $results[] = array( 129 | 'title' => 'Max Input Vars', 130 | 'value' => $max_input_vars, 131 | 'status' => ProcessDiagnostics::$ok, 132 | 'action' => '' 133 | ); 134 | 135 | // POST & Upload related :: Max Input Nesting Level 136 | $max_input_nesting_level = trim(ini_get('max_input_nesting_level')); 137 | $results[] = array( 138 | 'title' => 'Max Input Nesting Level', 139 | 'value' => $max_input_nesting_level, 140 | 'status' => ProcessDiagnostics::$ok, 141 | 'action' => '' 142 | ); 143 | 144 | 145 | // Debugger may slow down systems, lets have a check for loaded debuggers 146 | 147 | // XDebug, with special attention for "max_nesting_level" 148 | $xdebug = @extension_loaded('xdebug') || function_exists('xdebug_get_code_coverage'); 149 | if ($xdebug) { 150 | $xd_value = 'is loaded (*)'; 151 | $xd_nestingLevel = (int)ini_get('xdebug.max_nesting_level'); 152 | if ($xd_nestingLevel > 0 && $xd_nestingLevel < 190) { 153 | $xd_status = ProcessDiagnostics::$warn; 154 | $xd_action = sprintf('(*) xdebug.max_nesting_level = %d', $xd_nestingLevel); 155 | $xd_action .= '
' . sprintf('A value lower then 200 can result in problems, read more about it in the %s', 156 | 'PW Forums.'); 157 | } else { 158 | $xd_status = ProcessDiagnostics::$ok; 159 | $xd_action = sprintf('(*) xdebug.max_nesting_level = %d', $xd_nestingLevel); 160 | $xd_action .= '
' . sprintf('Read more on PW with XDebug in the %s', 161 | 'PW Forums.'); 162 | } 163 | 164 | $xd_action .= '
Debugger-Website: http://xdebug.org/'; 165 | $results[] = array( 166 | 'title' => 'XDebug Extension', 167 | 'value' => $xd_value, 168 | 'status' => $xd_status, 169 | 'action' => $xd_action 170 | ); 171 | } 172 | 173 | // DBG, - http://www.nusphere.com/dbg 174 | $dbg = @extension_loaded('dbg'); 175 | if ($dbg) { 176 | $results[] = array( 177 | 'title' => 'DBG Extension', 178 | 'value' => 'is loaded', 179 | 'status' => ProcessDiagnostics::$ok, 180 | 'action' => 'Debugger-Website: http://www.nusphere.com/dbg' 181 | ); 182 | } 183 | 184 | // Debug Bar, - http://phpdebugbar.com 185 | $debugbar = class_exists('StandardDebugBar'); 186 | if ($debugbar) { 187 | $results[] = array( 188 | 'title' => 'Debug Bar (JS-Implementation)', 189 | 'value' => 'is available (*)', 190 | 'status' => ProcessDiagnostics::$ok, 191 | 'action' => '(*) PHP Debug Bar is not a PHP-Extension but a PHP-Class that can be used to build JS-Code for viewing debugresults.' . 192 | '
Debugger-Website: http://phpdebugbar.com/' 193 | ); 194 | } 195 | 196 | // return all results 197 | return $results; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/Helpers/ProcessDiagnostics/ProcessDiagnostics.php: -------------------------------------------------------------------------------- 1 | = 7) { 54 | return number_format($b / 1048576, 2)." MB"; 55 | } elseif (strlen($b) >= 10) { 56 | return number_format($b / 1073741824, 2)." GB"; 57 | } 58 | return number_format($b / 1024, 2)." KB"; 59 | } 60 | 61 | /** 62 | * Reads basic FS parameters for the given file or directory. 63 | * 64 | * @param string $name 65 | * @param string $pathspec 66 | */ 67 | public static function getFileSystemAttribs($name, $pathspec) { 68 | $fs_info = array( 69 | 'name' => $name, 70 | 'path' => $pathspec, 71 | 'exists' => file_exists($pathspec), 72 | 'isfile' => false, 73 | 'islink' => false, 74 | 'isdir' => false, 75 | 'read' => false, 76 | 'write' => false, 77 | 'exec' => false, 78 | 'perms' => false, 79 | ); 80 | 81 | if ($fs_info['exists']) { 82 | $fs_info['isfile'] = is_file($pathspec); 83 | $fs_info['islink'] = is_link($pathspec); 84 | $fs_info['isdir'] = is_dir($pathspec); 85 | $fs_info['read'] = is_readable($pathspec); 86 | $fs_info['write'] = is_writable($pathspec); 87 | $fs_info['exec'] = is_executable($pathspec); 88 | $fs_info['perms'] = fileperms($pathspec); 89 | } 90 | 91 | return $fs_info; 92 | } 93 | 94 | /** 95 | * Choose Status 96 | * 97 | * @param array $warnings 98 | * @param array $fails 99 | */ 100 | public static function chooseStatus(array $warnings, array $fails) { 101 | if (count($fails)) return ProcessDiagnostics::$fail; 102 | if (count($warnings)) return ProcessDiagnostics::$warn; 103 | 104 | return ProcessDiagnostics::$ok; 105 | } 106 | 107 | /** 108 | * Creates a text description from the given file information. 109 | * 110 | * @param array $info 111 | */ 112 | public static function describeFSInfo($info) { 113 | $out = array(); 114 | 115 | if ($info['exists']) { 116 | $out[] = self::$exists; 117 | 118 | if ($info['read']) { 119 | $out[] = self::$read; 120 | } else { 121 | $out[] = self::$not_read; 122 | } 123 | 124 | if ($info['write']) { 125 | $out[] = self::$write; 126 | } else { 127 | $out[] = self::$not_write; 128 | } 129 | 130 | $out[] = substr(sprintf('%o', $info['perms']), -4); 131 | } else { 132 | $out[] = self::$not_exists; 133 | } 134 | 135 | return implode(', ', $out); 136 | } 137 | 138 | /** 139 | * Capitialise the initial character of the given string. 140 | * 141 | * @param string $string 142 | */ 143 | public static function initCap($string) { 144 | return strtoupper($string[0]) . substr($string, 1); 145 | } 146 | 147 | /** 148 | * returns if function is disabled in php 149 | * 150 | * @return boolean: true, false 151 | */ 152 | static public function isDisabled($function) { 153 | return in_array(strtolower($function), self::$disabled_functions); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/Helpers/PwTools.php: -------------------------------------------------------------------------------- 1 | setInput($input) 27 | ->setOutput($output); 28 | if ($checkForPW) { 29 | $this->bootstrapProcessWire(); 30 | } 31 | } 32 | 33 | /** 34 | * @param $email 35 | * @param $name 36 | * @param $userContainer 37 | * @param $pass 38 | * @return Page 39 | */ 40 | public function createUser($email, $name, $userContainer, $pass) { 41 | $user = new Page(); 42 | $user->template = 'user'; 43 | $user->setOutputFormatting(false); 44 | 45 | $user->parent = $userContainer; 46 | $user->name = $name; 47 | $user->title = $name; 48 | $user->pass = $pass; 49 | $user->email = $email; 50 | 51 | return $user; 52 | } 53 | 54 | /** 55 | * @param string $name 56 | * @param string $pass 57 | * @param string $email 58 | * @param string $newname 59 | * @return \Page 60 | */ 61 | public function updateUser($name, $pass, $email, $newname) { 62 | $user = \ProcessWire\wire('users')->get($name); 63 | $user->setOutputFormatting(false); 64 | 65 | if (!empty($newname)) { 66 | $name = \ProcessWire\wire('sanitizer')->username($newname); 67 | $user->name = $name; 68 | $user->title = $name; 69 | } 70 | 71 | if (!empty($pass)) $user->pass = $pass; 72 | 73 | if (!empty($email)) { 74 | $email = \ProcessWire\wire('sanitizer')->email($email); 75 | if ($email) $user->email = $email; 76 | } 77 | 78 | return $user; 79 | } 80 | 81 | /** 82 | * @param $user 83 | * @param $roles 84 | * @param $output 85 | * @param boolean $reset 86 | * @return mixed 87 | */ 88 | public function attachRolesToUser($user, $roles, $output, $reset = false) { 89 | $editedUser = \ProcessWire\wire('users')->get($user); 90 | 91 | // remove roles which are not submitted 92 | foreach ($editedUser->roles as $role) { 93 | if (!in_array($role->name, $roles)) { 94 | $editedUser->removeRole($role->name); 95 | } 96 | } 97 | $editedUser->save(); 98 | 99 | // remove existing roles 100 | if ($reset === true) { 101 | foreach ($editedUser->roles as $role) { 102 | $editedUser->removeRole($role->name); 103 | } 104 | } 105 | 106 | // add roles 107 | foreach ($roles as $role) { 108 | if ($role === 'guest') continue; 109 | $this->checkIfRoleExists($role, $output); 110 | 111 | $editedUser->addRole($role); 112 | $editedUser->save(); 113 | } 114 | 115 | return $editedUser; 116 | } 117 | 118 | /** 119 | * @param $role 120 | * @param $output 121 | * @return bool 122 | */ 123 | private function checkIfRoleExists($role, $output) { 124 | if (\ProcessWire\wire("pages")->get("name={$role}") instanceof \ProcessWire\NullPage) { 125 | $output->writeln("Role '{$role}' does not exist!"); 126 | 127 | return false; 128 | } 129 | } 130 | 131 | /** 132 | * Get available roles 133 | * 134 | * @param string $rls 135 | * @return array 136 | */ 137 | public function getAvailableRoles($rls) { 138 | $availableRoles = array(); 139 | foreach (\ProcessWire\wire('roles') as $role) $availableRoles[] = $role->name; 140 | 141 | return $rls ? explode(",", $rls) : $availableRoles; 142 | } 143 | 144 | public function getAvailableUsers($excludeGuest = false) { 145 | $availableUsers = array(); 146 | $usersObj = \ProcessWire\wire('users')->find('start=0')->sort('name'); 147 | foreach ($usersObj as $user) $availableUsers[] = $user->name; 148 | 149 | // filter out guest 150 | if ($excludeGuest) { 151 | $guestIndex = array_search('guest', $availableUsers); 152 | if ($guestIndex) unset($availableUsers[$guestIndex]); 153 | } 154 | 155 | return $availableUsers; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Helpers/WsTables.php: -------------------------------------------------------------------------------- 1 | output = $output; 27 | $this->tools = new Tools($output); 28 | } 29 | 30 | /** 31 | * Build borderless table 32 | * 33 | * @param array $content 34 | * @param array $headers 35 | */ 36 | public function buildTable($content, $headers) { 37 | if (!is_array($headers)) $headers = array($headers); 38 | foreach ($headers as $k => $header) { 39 | $headers[$k] = $this->tools->writeHeader($header, false); 40 | } 41 | 42 | $tablePW = new Table($this->output); 43 | $tablePW 44 | ->setStyle('borderless') 45 | ->setHeaders($headers) 46 | ->setRows($content); 47 | 48 | return $tablePW; 49 | } 50 | 51 | /** 52 | * Render Tables 53 | * 54 | * @param array $tables 55 | * @param boolean $nlBefore, default true 56 | */ 57 | public function renderTables($tables) { 58 | foreach ($tables as $table) { 59 | $table->render(); 60 | $this->output->writeln(''); 61 | } 62 | } 63 | 64 | /** 65 | * Write Table 66 | * 67 | * @param string $text 68 | */ 69 | public function writeTable($text) { 70 | $table = new Table($this->output); 71 | $table->setHeaders(array($text)); 72 | $table->render(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /wirecli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 3 |