Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'edit_languages' command #2576

Merged
merged 5 commits into from
Oct 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions src/Command/EditLanguagesCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php
namespace App\Command;

use App\Lib\LanguagesLib;
use App\Model\CurrentUser;
use Cake\Collection\Collection;
use Cake\Console\Arguments;
use Cake\Console\Command;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;

class EditLanguagesCommand extends Command
{
protected $log;

public function initialize() {
parent::initialize();
$this->loadModel('Sentences');
$this->loadModel('Users');
}

protected function buildOptionParser(ConsoleOptionParser $parser) {
$parser
->setDescription('Change sentence languages.')
->addArgument('username', [
'help' => 'Do all the work as given user who needs to be either ' .
'corpus maintainer or admin.',
'required' => true,
])
->addArgument('language', [
'help' => 'ISO code of the new language.',
'required' => true,
])
->addArgument('file', [
'help' => 'Name of the file that contains the sentence ids whose ' .
'language should be changed. ("stdin" will read from ' .
'standard input.)',
'required' => true,
]);
return $parser;
}

protected function editLanguage($ids, $lang) {
foreach ($ids as $id) {
$result = $this->Sentences->editSentence(compact('id', 'lang'));
if ($result) {
$this->log[] = "id $id - Language set to {$result->lang}";
} else {
$this->log[] = "id $id - Record not found or could not save changes";
}
}
}

public function execute(Arguments $args, ConsoleIo $io) {
$username = $args->getArgument('username');
$userId = $this->Users->getIdFromUsername($username);
if (!$userId) {
$io->error("User '$username' does not exist!");
$this->abort();
} else {
CurrentUser::store($this->Users->get($userId));
if (!CurrentUser::isModerator()) {
$io->error('User must be corpus maintainer or admin!');
$this->abort();
}
}

$input = $args->getArgument('file');
if ($input === 'stdin') {
$input = 'php://stdin';
} elseif ($input && !file_exists($input)) {
$io->error("File '$input' does not exist!");
$this->abort();
}

$newLanguage = $args->getArgument('language');
if (!LanguagesLib::languageExists($newLanguage)) {
$io->error("Language '$newLanguage' does not exist!");
$this->abort();
}

$this->log = [];
$ids = collection(file($input, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES))
->filter(function ($v) { return preg_match('/^\d+$/', $v); });
$total = $ids->count();

$this->editLanguage($ids, $newLanguage);

if (empty($this->log)) {
$io->out('There was nothing to do.');
} else {
$io->out("$total rows proceeded:");
foreach ($this->log as $logEntry) {
$io->out($logEntry);
}
}
}
}
78 changes: 14 additions & 64 deletions src/Model/Table/SentencesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -1200,26 +1200,19 @@ public function sphinxAttributesChanged(&$attributes, &$values, &$isMVA, $entity
/**
* Edit the sentence.
*
* @param array $data We're taking the data from the AJAX request. It has an
* 'id' and a 'value', but the 'id' actually contains the
* language followed by the id, separated by an underscore.
* @param array $data We're taking the data from the AJAX request. It should contain
* the key 'id' for the sentence ID and either 'text' or
* 'lang' or both.
*
* @return array
* @return Entity|false
*/
public function editSentence($data)
{
$text = $this->_getEditFormText($data);
$idLangArray = $this->_getEditFormIdLang($data);
if (!$text || !$idLangArray) {
return array();
}

// Set $id and $lang
extract($idLangArray);
$id = (int)$data['id'] ?? null;
try {
$sentence = $this->get($id);
} catch (RecordNotFoundException $e) {
return array();
return false;
}

if ($this->_cantEditSentence($sentence)) {
Expand All @@ -1230,58 +1223,15 @@ public function editSentence($data)
return $sentence;
}

$this->patchEntity($sentence, [
'text' => $text,
'lang' => $lang
]);

$sentenceSaved = $this->save($sentence);
if ($sentenceSaved) {
$this->UsersSentences->makeDirty($id);
}

return $sentenceSaved;
}

/**
* Get text from edit form params.
*
* @param array $params Form parameters.
*
* @return string|boolean
*/
private function _getEditFormText($params)
{
if (isset($params['value'])) {
return trim($params['value']);
}

return false;
}

/**
* Get id and lang from edit form params.
*
* @param array $params Form parameters.
*
* @return array|boolean
*/
private function _getEditFormIdLang($params)
{
if (isset($params['id'])) {
// ['form']['id'] contains both the sentence id and its language.
// Do not sanitize it directly.
$sentenceId = $params['id'];

$dirtyArray = explode("_", $sentenceId);

return [
'id' => (int)$dirtyArray[1],
'lang' => $dirtyArray[0]
];
$this->patchEntity($sentence, $data, ['fields' => ['text', 'lang']]);
if ($sentence->isDirty()) {
$sentenceSaved = $this->save($sentence);
if ($sentenceSaved) {
$this->UsersSentences->makeDirty($id);
}
return $sentenceSaved;
}

return false;
return $sentence;
}

/**
Expand Down
4 changes: 0 additions & 4 deletions src/View/Helper/SentencesHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -689,14 +689,10 @@ public function displaySentenceText(
if ($isEditable) {
$classes[] = 'editableSentence';

// TODO: HACK SPOTTED id is used in edit_in_place
// NOTE: I didn't find an easy way to pass the sentenceId to jEditable
// using jQuery.data...
echo $this->Languages->tagWithLang(
'div', $sentenceLang, $sentenceText,
array(
'class' => join(' ', $classes),
'id' => $sentenceLang.'_'.$sentenceId,
/* @translators: submit button of sentence edition form */
'data-submit' => __('OK'),
/* @translators: cancel button of sentence edition form (verb) */
Expand Down
121 changes: 121 additions & 0 deletions tests/TestCase/Command/EditLanguagesCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php
namespace App\Test\TestCase\Command;

use Cake\Console\Command;
use Cake\Filesystem\File;
use Cake\Filesystem\Folder;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\ConsoleIntegrationTestTrait;
use Cake\TestSuite\TestCase;

class EditLanguagesCommandTest extends TestCase
{
use ConsoleIntegrationTestTrait;

public $fixtures = [
'app.audios',
'app.contributions',
'app.languages',
'app.links',
'app.reindex_flags',
'app.sentences',
'app.tags',
'app.tags_sentences',
'app.transcriptions',
'app.users',
'app.users_languages',
'app.users_sentences',
];

const TESTDIR = TMP . 'edit_languages_tests' . DS;

public static function setUpBeforeClass() {
new Folder(self::TESTDIR, true, 0755);
}

public static function tearDownAfterClass() {
$folder = new Folder(self::TESTDIR);
$folder->delete();
}

public function setUp() {
parent::setUp();
$this->UseCommandRunner();
$this->Sentences = TableRegistry::getTableLocator()->get('Sentences');
}

private function create_test_file($ids) {
$path = self::TESTDIR . 'input_test';
$file = new File($path);
$file->write(implode("\n", $ids));
$file->close();
return $path;
}

public function testExecute_changesLanguage() {
$path = $this->create_test_file([1, 2]);
$this->exec("edit_languages admin fra $path");

$this->assertExitCode(Command::CODE_SUCCESS);

$sentence = $this->Sentences->get(2);
$this->assertEquals('fra', $sentence->lang);
$this->assertEquals("259east\0\0\0\0\0\0\0\0\0", $sentence->hash);
}

public function successesProvider() {
// username, language, ids, number of changes
return [
'all ids changed to fra' =>
['admin', 'fra', [1, 2, 5], 3],
'some ids changed to fra' =>
['admin', 'fra', [1, 3, 5, 8], 2],
'with wrong ids' =>
['admin', 'fra', [1, 99, 999], 1],
'empty file' => ['admin', 'fra', [], 0],
];
}

private function countLanguage($ids, $lang) {
if (empty($ids)) {
return 0;
} else {
return $this->Sentences
->find()
->where(['id IN' => $ids, 'lang' => $lang])
->count();
}
}

/**
* @dataProvider successesProvider
**/
public function testExecute_severalScenarios($user, $newLanguage, $ids, $changes) {
$path = $this->create_test_file($ids);
$before = $this->countLanguage($ids, $newLanguage);
$this->exec("edit_languages $user $newLanguage $path");
$after = $this->countLanguage($ids, $newLanguage);
$this->assertExitCode(Command::CODE_SUCCESS);
$this->assertEquals($changes, $after - $before);
}

public function failuresProvider() {
return [
'without any required argument' => ['edit_languages'],
'without language' => ['edit_languages admin'],
'without file' => ['edit_languages admin eng'],
'with unknown file' => ['edit_languages admin eng unknown_file'],
'with invalid language' => ['edit_languages admin invalid stdin'],
'as unknown user' => ['edit_languages unknown_user eng stdin'],
'as non-moderator' => ['edit_languages contributor eng stdin'],
];
}

/**
* @dataProvider failuresProvider
*/
public function testExecute_failures($command) {
$this->exec($command);
$this->assertExitCode(Command::CODE_ERROR);
}
}
2 changes: 1 addition & 1 deletion tests/TestCase/Controller/SentencesControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ public function testAddSentence_WithLicense($user, $data, $expectedLicense) {
public function testEditSentence_doesntWorkForUnknownSentence() {
$this->logInAs('contributor');
$this->ajaxPost('/jpn/sentences/edit_sentence', [
'id' => 'epo_999999', 'value' => 'Forlasu!',
'id' => '999999', 'lang' => 'epo', 'text' => 'Forlasu!',
]);
$this->assertRedirect('/jpn/home');
}
Expand Down
Loading