Skip to content

Commit

Permalink
CLI wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fisharebest committed Apr 3, 2024
1 parent 9c2569b commit bb87bb9
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 24 deletions.
87 changes: 87 additions & 0 deletions app/Cli/Commands/TreeCreate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

/**
* webtrees: online genealogy
* Copyright (C) 2023 webtrees development team
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

declare(strict_types=1);

namespace Fisharebest\Webtrees\Cli\Commands;

use Fisharebest\Webtrees\Contracts\TreeInterface;
use Fisharebest\Webtrees\DB;
use Fisharebest\Webtrees\Services\TreeService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

use function bin2hex;
use function random_bytes;

class TreeCreate extends Command
{
public function __construct(private readonly TreeService $tree_service)
{
parent::__construct();
}

protected function configure(): void
{
$this
->setName(name: 'tree-create')
->setDescription(description: 'Create a new tree')
->addOption(name: 'name', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'The name of the new tree')
->addOption(name: 'title', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'The title of the new tree');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle(input: $input, output: $output);

$name = $input->getOption(name: 'name');
$title = $input->getOption(name: 'title');

$missing = false;

if ($name === null) {
$io->error(message: 'Missing required option: --name');
$missing = true;
}

if ($title === null) {
$io->error(message: 'Missing required option: --title');
$missing = true;
}

if ($missing) {
return Command::INVALID;
}

$tree = $this->tree_service->all()[$name] ?? null;

if ($tree !== null) {
$io->error(message: 'A tree with the name "' . $name . '" already exists.');

return Command::FAILURE;
}

$tree = $this->tree_service->create(name: $name, title: $title);

DB::exec(sql: 'COMMIT');

return Command::SUCCESS;
}
}
98 changes: 98 additions & 0 deletions app/Cli/Commands/TreeExport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

/**
* webtrees: online genealogy
* Copyright (C) 2023 webtrees development team
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

declare(strict_types=1);

namespace Fisharebest\Webtrees\Cli\Commands;

use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\DB;
use Fisharebest\Webtrees\Encodings\UTF8;
use Fisharebest\Webtrees\Services\GedcomExportService;
use Fisharebest\Webtrees\Services\TreeService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

use function addcslashes;
use function stream_get_contents;

class TreeExport extends Command
{
public function __construct(
private readonly GedcomExportService $gedcom_export_service,
private readonly TreeService $tree_service,
) {
parent::__construct();
}

protected function configure(): void
{
$this
->setName(name: 'tree-export')
->addArgument(name: 'tree_name', mode: InputArgument::REQUIRED, description: 'The name of the tree', suggestedValues: self::autoCompleteTreeName(...))
->addOption(name: 'format', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'Export format')
->addOption(name: 'filename', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'Export filename')
->setDescription(description: 'Export a tree to a GEDCOM file');
}

private function autoCompleteTreeName(CompletionInput $input): array
{
return DB::table('tree')
->where('tree_name', 'LIKE', addcslashes($input->getCompletionValue(), '%_\\') .'%')
->pluck('name')
->all();
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle(input: $input, output: $output);

$tree_name = $input->getArgument(name: 'tree_name');
$format = $input->getOption(name: 'format');
$filename = $input->getOption(name: 'filename');

$tree = $this->tree_service->all()[$tree_name] ?? null;

if ($tree === null) {
$io->error(message: 'Tree "' . $tree_name . '" not found.');

return Command::FAILURE;
}

$stream = $this->gedcom_export_service->export(
tree: $tree,
sort_by_xref: false,
encoding: UTF8::NAME,
access_level: Auth::PRIV_HIDE,
line_endings: 'CRLF',
records: null,
zip_filesystem: null,
media_path: null,
);

echo stream_get_contents($stream);

$io->success('File exported successfully.');

return Command::SUCCESS;
}
}
63 changes: 63 additions & 0 deletions app/Cli/Commands/TreeList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

/**
* webtrees: online genealogy
* Copyright (C) 2023 webtrees development team
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

declare(strict_types=1);

namespace Fisharebest\Webtrees\Cli\Commands;

use Fisharebest\Webtrees\Services\TreeService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class TreeList extends Command
{
public function __construct(private readonly TreeService $tree_service)
{
parent::__construct();
}

protected function configure(): void
{
$this
->setName(name: 'tree-list')
->setDescription(description: 'List trees');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$trees = $this->tree_service->all()->sort(fn($a, $b) => $a->id() <=> $b->id());

$table = new Table(output: $output);

$table->setHeaders(headers: ['ID', 'Name', 'Title', 'Imported']);

foreach ($trees as $tree) {
$table->addRow(row: [
$tree->id(),
$tree->name(),
$tree->title(),
$tree->getPreference(setting_name: 'imported') ? 'Yes' : 'No'
]);
}

$table->render();

return Command::SUCCESS;
}
}
39 changes: 25 additions & 14 deletions app/Cli/Commands/UserCreate.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@

namespace Fisharebest\Webtrees\Cli\Commands;

use Composer\Console\Input\InputOption;
use Fisharebest\Webtrees\Contracts\UserInterface;
use Fisharebest\Webtrees\DB;
use Fisharebest\Webtrees\Services\UserService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

Expand All @@ -43,12 +42,14 @@ protected function configure(): void
{
$this
->setName(name: 'user-create')
->setDescription(description: 'Create a new user account')
->addOption(name: 'admin', shortcut: 'a', mode: InputOption::VALUE_NONE, description: 'Make the new user an administrator')
->addOption(name: 'username', shortcut: 'u', mode: InputOption::VALUE_REQUIRED, description: 'The username of the new user')
->addOption(name: 'realname', shortcut: 'r', mode: InputOption::VALUE_REQUIRED, description: 'The real name of the new user')
->addOption(name: 'email', shortcut: 'e', mode: InputOption::VALUE_REQUIRED, description: 'The email of the new user')
->addOption(name: 'password', shortcut: 'p', mode: InputOption::VALUE_OPTIONAL, description: 'The password of the new user');
->setDescription(description: 'Create a new user')
->addOption(name: 'username', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'The username of the new user')
->addOption(name: 'realname', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'The real name of the new user')
->addOption(name: 'email', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'The email of the new user')
->addOption(name: 'password', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'The password of the new user')
->addOption(name: 'timezone', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'Set the timezone', default: 'UTC')
->addOption(name: 'language', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'Set the language', default: 'en-US')
->addOption(name: 'admin', shortcut: null, mode: InputOption::VALUE_NONE, description: 'Make the new user an administrator');
}

protected function execute(InputInterface $input, OutputInterface $output): int
Expand All @@ -60,26 +61,33 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$email = $input->getOption(name: 'email');
$password = $input->getOption(name: 'password');
$admin = (bool) $input->getOption(name: 'admin');
$timezone = $input->getOption(name: 'timezone');
$language = $input->getOption(name: 'language');

$missing = false;
$errors = false;

if ($username === null) {
$io->error(message: 'Missing required option: --username');
$missing = true;
$errors = true;
}

if ($realname === null) {
$io->error(message: 'Missing required option: --realname');
$missing = true;
$errors = true;
}

if ($email === null) {
$io->error(message: 'Missing required option: --email');
$missing = true;
$errors = true;
}

if ($missing) {
return Command::FAILURE;
if ($timezone === null) {
$io->error(message: 'Missing required option: --timezone');
$errors = true;
}

if ($errors) {
return Command::INVALID;
}

$user = $this->user_service->findByUserName(user_name: $username);
Expand All @@ -104,8 +112,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

$user = $this->user_service->create(user_name: $username, real_name:$realname, email: $email, password: $password);
$user->setPreference(setting_name: UserInterface::PREF_TIME_ZONE, setting_value: $timezone);
$user->setPreference(setting_name: UserInterface::PREF_LANGUAGE, setting_value: $language);
$user->setPreference(setting_name: UserInterface::PREF_IS_ACCOUNT_APPROVED, setting_value: '1');
$user->setPreference(setting_name: UserInterface::PREF_IS_EMAIL_VERIFIED, setting_value: '1');
$user->setPreference(setting_name: UserInterface::PREF_CONTACT_METHOD, setting_value: 'messaging');
$io->success('User ' . $user->id() . ' created.');

if ($admin) {
Expand Down
12 changes: 2 additions & 10 deletions app/Cli/Commands/UserList.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,14 @@

namespace Fisharebest\Webtrees\Cli\Commands;

use Composer\Console\Input\InputOption;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Contracts\UserInterface;
use Fisharebest\Webtrees\DB;
use Fisharebest\Webtrees\Registry;
use Fisharebest\Webtrees\Services\UserService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

use function bin2hex;
use function random_bytes;

class UserList extends Command
{
Expand All @@ -51,13 +44,11 @@ protected function configure(): void

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle(input: $input, output: $output);

$users = $this->user_service->all()->sort(fn($a, $b) => $a->id() <=> $b->id());

$table = new Table(output: $output);

$table->setHeaders(headers: ['ID', 'Username', 'Real Name', 'Email', 'Admin', 'Approved', 'Verified', 'Language', 'Time zone', 'Registered', 'Last login']);
$table->setHeaders(headers: ['ID', 'Username', 'Real Name', 'Email', 'Admin', 'Approved', 'Verified', 'Language', 'Timezone', 'Contact', 'Registered', 'Last login']);

foreach ($users as $user) {
$registered = (int) $user->getPreference(setting_name: UserInterface::PREF_TIMESTAMP_REGISTERED);
Expand Down Expand Up @@ -85,6 +76,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$user->getPreference(setting_name: UserInterface::PREF_IS_EMAIL_VERIFIED) ? 'Yes' : 'No',
$user->getPreference(setting_name: UserInterface::PREF_LANGUAGE),
$user->getPreference(setting_name: UserInterface::PREF_TIME_ZONE),
$user->getPreference(setting_name: UserInterface::PREF_CONTACT_METHOD),
$registered,
$last_login,
]);
Expand Down
5 changes: 5 additions & 0 deletions app/Cli/Console.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@

final class Console extends Application
{
public function __construct()
{
parent::__construct(Webtrees::NAME, Webtrees::VERSION);
}

public function loadCommands(): self
{
$commands = glob(pattern: __DIR__ . '/Commands/*.php') ?: [];
Expand Down

0 comments on commit bb87bb9

Please sign in to comment.