Skip to content
Snippets Groups Projects
Commit 1457e2b8 authored by Sebastien ANDRE's avatar Sebastien ANDRE
Browse files

hotlien_133563_add_var_to_index_translated_article

parent 621e91e6
Branches
1 merge request!5183[RTKO] hotlien_133563_add_var_to_index_translated_article
Pipeline #49208 passed with stage
- correctif #133563 : Cosmogramme : Indexation des Articles, traduire les articles en fonction de la langue par défaut.
\ No newline at end of file
......@@ -28,6 +28,8 @@ class Class_Cosmogramme_Integration_PhasePseudoRecordCms
protected $_label = 'Pseudo-notices : CMS';
protected $_model_name = Class_Article::class;
protected ?string $_current_date = null;
protected ?array $_indexable_languages = null;
/** @return array **/
protected function _previousPhaseIds()
......@@ -35,22 +37,19 @@ class Class_Cosmogramme_Integration_PhasePseudoRecordCms
return [0.1];
}
public function isTimeOut()
{
return false;
}
protected function _execute()
{
$this->_deleteOrphansRecords()
->_unindexUnpublished()
->_unindexAll()
->_indexPublished()
->_summarize();
}
protected function _deleteOrphansRecords(): self
{
Class_Notice::deleteQuery()
......@@ -60,28 +59,99 @@ class Class_Cosmogramme_Integration_PhasePseudoRecordCms
->on_eq('id_notice', 'id_notice'))
->deleteAll();
Class_Notice::clearCache();
return $this;
}
protected function _unindexUnpublished(): self
protected function _unindexAll(): self
{
$ids = Zend_Registry::get('sql')
->fetchAllByColumn('select n.id_notice '
. $this->_articlesRecordsJoin()
. ' where (a.debut is not null and date(a.debut) > date(now())) or (a.fin is not null and date(a.fin) < date(now()))');
$records_ids = array_unique([...$this->_unindexUnpublished(),
...$this->_unindexUntranslated(),
...$this->_unindexUntranslatedOnExemplaire()]);
$this->_deleteRecorsItemsAndLog($records_ids)
->_updateArticlesWithoutRecords();
return $this;
}
if ( ! $ids)
{
protected function _unindexUnpublished(): array
{
$ids = Class_Article::query()
->select(['id_article', 'id_notice'])
->beOr()
->eq('indexation', 0)
->gt('debut', $this->_currentDate())
->and((new Storm_Query_Criteria)
->not_is_null('fin')
->lt('fin', $this->_currentDate()))
->beBasic()
->fetchAll();
if ( ! $ids) {
$this->_log->info($this->_('Aucun article à désindexer'));
return $this;
return [];
}
Class_Notice::basicDeleteBy(['id_notice' => $ids]);
Class_Exemplaire::basicDeleteBy(['id_notice' => $ids]);
$translates = Class_Article::query()
->select('id_notice')
->gt('id_notice', 0)
->in('parent_id', array_map(fn($row) => $row['id_article'],
$ids))
->beBasic()
->fetchAll();
return array_filter(array_merge(array_map(fn($row) => $row['id_notice'],
$ids),
array_map(fn($row) => $row['id_notice'],
$translates)));
}
$count = count($ids);
protected function _unindexUntranslated(): array
{
$query = Class_Article::query()
->select('id_notice')
->gt('id_notice', 0)
->gt('langue', '')
->beBasic();
return array_map(fn($row) => $row['id_notice'],
$query->not_in('langue', $this->_indexableLanguages())
->fetchAll());
}
protected function _unindexUntranslatedOnExemplaire(): array
{
$rows = Class_Exemplaire::query()
->select('id_notice')
->gt('id_notice', 0)
->gt('id_origine', 0)
->exists(Class_Notice::subExists('records')
->on_eq('id_notice', 'id_notice')
->eq('type_doc', Class_TypeDoc::ARTICLE)
->eq('type', Class_Notice::TYPE_BIBLIOGRAPHIC))
->exists(Class_Article::subExists('articles')
->on_eq('id_origine', 'id_article')
->not_in('langue', $this->_indexableLanguages()))
->beBasic()
->fetchAll();
return array_map(fn($row) => $row['id_notice'], $rows);
}
protected function _deleteRecorsItemsAndLog(array $records_ids): self
{
if ($records_ids) {
Class_Notice::basicDeleteBy(['id_notice' => $records_ids]);
Class_Exemplaire::basicDeleteBy(['id_notice' => $records_ids]);
Class_Notice::clearCache();
Class_Exemplaire::clearCache();
}
$count = count($records_ids);
$this->_log->info($this->_plural($count,
'Aucun article à désindexer',
'1 article désindexé',
......@@ -91,28 +161,71 @@ class Class_Cosmogramme_Integration_PhasePseudoRecordCms
return $this;
}
protected function _updateArticlesWithoutRecords(): self
{
Class_Article::updateQuery()
->addSet('id_notice', null)
->gt('id_notice', 0)
->not_exists(Class_Notice::subExists()
->on_eq('id_notice', 'id_notice'))
->updateAll();
Class_Article::clearCache();
return $this;
}
protected function _indexPublished(): self
{
$published_select = 'select a.id_article ' . $this->_articlesRecordsJoin();
$sql = 'select id_article from cms_article where id_article not in ('. $published_select . ') '
. 'and ((debut is null or date(debut) <= date(now())) and (fin is null or date(fin) >= date(now()))) '
. 'and status='. Class_Article::STATUS_VALIDATED . ' and indexation=1 '
. 'order by id_article';
if ($ids = Zend_Registry::get('sql')->fetchAllByColumn($sql))
{
$count = count($ids);
$this->_log->info($this->_plural($count,
'Aucun article à indexer',
'1 article à indexer',
'%d articles à indexer',
$count));
if ( ! ($art_rows = Class_Article::query()
->select(['id_article', 'langue'])
->eq('indexation', 1)
->is_null('id_notice')
->eq('parent_id', 0)
->eq('status', Class_Article::STATUS_VALIDATED)
->or((new Storm_Query_Criteria)
->is_null('debut')
->lt_eq('debut', $this->_currentDate()))
->or((new Storm_Query_Criteria)
->is_null('fin')
->gt_eq('fin', $this->_currentDate()))
->beBasic()
->fetchAll())) {
$this->_log->info($this->_('Aucun article à indexer'));
return $this;
}
foreach (array_chunk($ids, 100) as $page_ids)
{
$art_origin_ids = array_filter(array_map(function($row) {
$langue = $row['langue'] ?? '';
return (( ! $langue || in_array($langue, $this->_indexableLanguages()))
? $row['id_article']
: null);
},
$art_rows));
$trad_ids = Class_Article::query()
->select('id_article')
->in('parent_id', array_map(fn($row) => $row['id_article'],
$art_rows))
->is_null('id_notice')
->in('langue', $this->_indexableLanguages())
->beBasic()
->fetchAll();
$art_origin_ids = [...$art_origin_ids,
...array_map(fn($trad) => $trad['id_article'],
$trad_ids)];
$count = count($art_origin_ids);
$this->_log->info($this->_plural($count,
'Aucun article à indexer',
'1 article à indexer',
'%d articles à indexer',
$count));
foreach (array_chunk($art_origin_ids, 100) as $page_ids) {
$this->_runPage(Class_Article::findAllBy(['id_article' => $page_ids]));
$this->_cleanMemory();
}
......@@ -120,11 +233,13 @@ class Class_Cosmogramme_Integration_PhasePseudoRecordCms
return $this;
}
protected function _currentDate(): string
{
return $this->_current_date ??= static::getCurrentDate();
}
protected function _articlesRecordsJoin(): string
protected function _indexableLanguages(): array
{
return 'from notices as n '
. 'inner join exemplaires as e on (n.id_notice=e.id_notice and n.type=1 and n.type_doc="' . Class_TypeDoc::ARTICLE . '") '
. 'inner join cms_article as a on a.id_article=e.id_origine or a.parent_id = e.id_origine';
return $this->_indexable_languages ??= [Class_AdminVar::getDefaultLanguage()];
}
}
Subproject commit 919691187a60a735fee3bcdc81e1b688460b5a63
Subproject commit b6e707284640d3abba44c18d83b3a0f6895b1870
<?php
/**
* Copyright (c) 2012-2025, Agence Française Informatique (AFI). All rights reserved.
*
* BOKEH is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* BOKEH 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with BOKEH; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
class PhasePseudoRecordCmsLanguesTest
extends Class_Cosmogramme_Integration_PhaseTestCase
{
protected array $_storm_scopes = ['PhasePseudoRecordCmsLanguesTest'];
protected function _getPreviousPhase()
{
return (new Class_Cosmogramme_Integration_Phase(0.1))
->beCron();
}
public function setUp(): void
{
parent::setUp();
Class_Cosmogramme_Integration_PhasePseudoRecordCms::setTimeSource(new TimeSourceForTest('2025-03-20 14:00:00'));
Class_AdminVar::set('DEFAULT_LANGUAGE', 'fr');
Class_AdminVar::set('LANGUES', 'en;');
Class_AdminVar::set('WORKFLOW', 1);
$this->fixture(Class_Notice::class,
['id' => 159,
'titres' => 'TRANSLATION ARTICLE',
'type' => 1,
'type_doc' => 8]);
$this->fixture(Class_Notice::class,
['id' => 160,
'titres' => 'ORPHAN RECORD',
'type' => 1,
'type_doc' => 8]);
$this->fixture(Class_Exemplaire::class,
['id' => 753,
'id_notice' => 159,
'id_origine' => 22299]);
$this->fixture(Class_Exemplaire::class,
['id' => 754,
'id_notice' => 160,
'id_origine' => 22300]);
$this->fixture(Class_Article::class,
['id' => 22199,
'parent_id' => 0,
'titre' => 'Article en francais',
'contenu' => 'Article en francais',
'status' => Class_Article::STATUS_VALIDATED,
'debut' => null,
'fin' => null,
'indexation' => 1,
'langue' => 'fr']);
$this->fixture(Class_Article::class,
['id' => 22299,
'id_notice' => 159,
'titre' => 'translation article',
'contenu' => 'translation article',
'status' => Class_Article::STATUS_VALIDATED,
'indexation' => 1,
'langue' => 'en',
'parent_id' => 22199]);
$this->_phase = $this->_buildPhase('PseudoRecordCms')
->setMemoryCleaner(function(): void {})
->run();
Class_Notice::clearCache();
Class_Exemplaire::clearCache();
Class_Article::clearCache();
}
public function tearDown(): void
{
Class_Cosmogramme_Integration_PhasePseudoRecordCms::setTimeSource(null);
parent::tearDown();
}
/** @test */
public function requestPhaseCmsShouldExists()
{
$this->assertSqlEquals(["SELECT `notices`.* FROM `notices` WHERE (`notices`.`type_doc` = 8 AND `notices`.`type` = 1 AND NOT EXISTS (SELECT 'x' FROM `cms_article` AS `articles` WHERE (`notices`.`id_notice` = `articles`.`id_notice`))) LIMIT 100",
"SELECT `cms_article`.`id_article`, `cms_article`.`id_notice` FROM `cms_article` WHERE (`cms_article`.`indexation` = 0 OR `cms_article`.`debut` > '2025-03-20' OR (`cms_article`.`fin` IS NOT null AND `cms_article`.`fin` < '2025-03-20'))",
"SELECT `cms_article`.`id_notice` FROM `cms_article` WHERE (`cms_article`.`id_notice` > 0 AND `cms_article`.`langue` > '' AND `cms_article`.`langue` NOT IN ('fr'))",
"SELECT `exemplaires`.`id_notice` FROM `exemplaires` WHERE (`exemplaires`.`id_notice` > 0 AND `exemplaires`.`id_origine` > 0 AND EXISTS (SELECT 'x' FROM `notices` AS `records` WHERE (`exemplaires`.`id_notice` = `records`.`id_notice` AND `records`.`type_doc` = 8 AND `records`.`type` = 1)) AND EXISTS (SELECT 'x' FROM `cms_article` AS `articles` WHERE (`exemplaires`.`id_origine` = `articles`.`id_article` AND `articles`.`langue` NOT IN ('fr'))))",
"UPDATE `cms_article` SET `id_notice` = null WHERE (`cms_article`.`id_notice` > 0 AND NOT EXISTS (SELECT 'x' FROM `notices` WHERE (`cms_article`.`id_notice` = `notices`.`id_notice`)))",
"SELECT `cms_article`.`id_article`, `cms_article`.`langue` FROM `cms_article` WHERE (`cms_article`.`indexation` = 1 AND `cms_article`.`id_notice` IS null AND `cms_article`.`parent_id` = 0 AND `cms_article`.`status` = 3 AND (`cms_article`.`debut` IS null OR `cms_article`.`debut` <= '2025-03-20') AND (`cms_article`.`fin` IS null OR `cms_article`.`fin` >= '2025-03-20'))",
"SELECT `cms_article`.`id_article` FROM `cms_article` WHERE (`cms_article`.`parent_id` IN (22199) AND `cms_article`.`id_notice` IS null AND `cms_article`.`langue` IN ('fr'))"]);
}
/** @test */
public function oldRecordShouldBeDeleted()
{
$this->assertNull(Class_Notice::find(159));
$this->assertNull(Class_Notice::find(160));
}
/** @test */
public function oldItemsShouldBeDeleted()
{
$this->assertNull(Class_Exemplaire::find(753));
$this->assertNull(Class_Exemplaire::find(754));
}
/** @test */
public function recordShouldHaveTitresTestingArticle()
{
$this->assertEquals('ARTICLE EN FRANCAIS ARTIKL AN FRANKAI',
Class_Notice::find(1)->getTitres());
}
/** @test */
public function article21199ShouldHaveIdNotice1()
{
$this->assertEquals(1, Class_Article::find(22199)->getIdNotice());
}
/** @test */
public function article21299ShouldNotHaveIdNoticeAnymore()
{
$this->assertNull(Class_Article::find(22299)->getIdNotice());
}
}
......@@ -20,27 +20,26 @@
*/
class PhasePseudoRecordCmsUnindexTest extends Class_Cosmogramme_Integration_PhaseTestCase {
class PhasePseudoRecordCmsUnindexTest extends Class_Cosmogramme_Integration_PhaseTestCase
{
protected function _getPreviousPhase() {
protected function _getPreviousPhase()
{
return (new Class_Cosmogramme_Integration_Phase(0.1))
->beCron();
}
public function setUp(): void {
public function setUp(): void
{
parent::setUp();
Zend_Registry::set('sql',
$this->mock()
->whenCalled('fetchAllByColumn')->answers([])
->whenCalled('fetchAllByColumn')
->with('select n.id_notice from notices as n inner join exemplaires as e on (n.id_notice=e.id_notice and n.type=1 and n.type_doc="8") inner join cms_article as a on a.id_article=e.id_origine or a.parent_id = e.id_origine where (a.debut is not null and date(a.debut) > date(now())) or (a.fin is not null and date(a.fin) < date(now()))')
->answers([12]));
Class_Cosmogramme_Integration_PhasePseudoRecordCms::setTimeSource(new TimeSourceForTest('2022-05-30 14:00:00'));
Class_AdminVar::set('WORKFLOW', 1);
$this->fixture(Class_Article::class,
['id' => 22199,
'id_notice' => 12,
'titre' => 'Testing article',
'contenu' => 'Testing article',
'status' => Class_Article::STATUS_VALIDATED,
......@@ -60,62 +59,68 @@ class PhasePseudoRecordCmsUnindexTest extends Class_Cosmogramme_Integration_Phas
$this->_phase = $this->_buildPhase('PseudoRecordCms')
->setMemoryCleaner(function(): void {})
->run();
Class_Notice::clearCache();
Class_Exemplaire::clearCache();
Class_Article::clearCache();
}
public function tearDown(): void
{
Class_Cosmogramme_Integration_PhasePseudoRecordCms::setTimeSource(null);
parent::tearDown();
}
/** @test */
public function shouldNotBeError() {
public function shouldNotBeError()
{
$this->assertNotError();
}
/** @test */
public function record12ShouldBeDeleted() {
public function record12ShouldBeDeleted()
{
$this->assertNull(Class_Notice::findFirstBy([]));
}
/** @test */
public function item12ShouldBeDeleted() {
public function item12ShouldBeDeleted()
{
$this->assertNull(Class_Exemplaire::findFirstBy([]));
}
/** @test */
public function logShouldContains1ArticleDésindexé() {
public function logShouldContains1ArticleDésindexé()
{
$this->assertLogContains('1 article désindexé');
}
/** @test */
public function logShouldContainsAucuneNoticeATraiter() {
$this->assertLogContains('Aucune notice à traiter');
}
}
class PhasePseudoRecordCmsIndexTest extends Class_Cosmogramme_Integration_PhaseTestCase {
class PhasePseudoRecordCmsIndexTest extends Class_Cosmogramme_Integration_PhaseTestCase
{
protected function _getPreviousPhase() {
protected function _getPreviousPhase()
{
return (new Class_Cosmogramme_Integration_Phase(0.1))
->beCron();
}
public function setUp(): void {
public function setUp(): void
{
parent::setUp();
Zend_Registry::set('sql',
$this->mock()
->whenCalled('fetchAllByColumn')->answers([])
->whenCalled('fetchAllByColumn')
->with('select id_article from cms_article where id_article not in (select a.id_article from notices as n inner join exemplaires as e on (n.id_notice=e.id_notice and n.type=1 and n.type_doc="8") inner join cms_article as a on a.id_article=e.id_origine or a.parent_id = e.id_origine) and ((debut is null or date(debut) <= date(now())) and (fin is null or date(fin) >= date(now()))) and status=3 and indexation=1 order by id_article')
->answers([22199]));
Class_Article::setTimeSource(new TimeSourceForTest('2022-04-30 14:50:49'));
Class_Cosmogramme_Integration_PhasePseudoRecordCms::setTimeSource(new TimeSourceForTest('2022-04-30 14:00:00'));
Class_AdminVar::set('WORKFLOW', 1);
$this->fixture(Class_Article::class,
['id' => 22199,
......@@ -133,38 +138,45 @@ class PhasePseudoRecordCmsIndexTest extends Class_Cosmogramme_Integration_PhaseT
}
public function tearDown(): void {
public function tearDown(): void
{
Class_Article::setTimeSource(null);
Class_Cosmogramme_Integration_PhasePseudoRecordCms::setTimeSource(null);
parent::tearDown();
}
/** @test */
public function shouldNotBeError() {
public function shouldNotBeError()
{
$this->assertNotError();
}
/** @test */
public function recordShouldBeCreated() {
public function recordShouldBeCreated()
{
$this->assertNotNull(Class_Notice::findFirstBy(['type_doc' => Class_TypeDoc::ARTICLE]));
}
/** @test */
public function itemShouldBeCreated() {
public function itemShouldBeCreated()
{
$this->assertNotNull(Class_Exemplaire::findFirstBy(['id_origine' => 22199]));
}
/** @test */
public function logShouldContainsAucunArticleADésindexer() {
public function logShouldContainsAucunArticleADésindexer()
{
$this->assertLogContains('Aucun article à désindexer');
}
/** @test */
public function logShouldContains1NoticeTraitee() {
public function logShouldContains1NoticeTraitee()
{
$this->assertLogContains('1 notice(s) traitée(s)');
}
}
......@@ -172,88 +184,10 @@ class PhasePseudoRecordCmsIndexTest extends Class_Cosmogramme_Integration_PhaseT
/* https://forge.afi-sa.net/issues/133563 */
class PhasePseudoRecordCmsIndexWithTranslatedRecordTest
extends Class_Cosmogramme_Integration_PhaseTestCase {
protected function _getPreviousPhase() {
return (new Class_Cosmogramme_Integration_Phase(0.1))
->beCron();
}
public function setUp(): void {
parent::setUp();
Class_AdminVar::set('DEFAULT_LANGUAGE', 'fr');
Class_AdminVar::set('LANGUES', 'en;');
Zend_Registry::set('sql',
$this->mock()
->whenCalled('fetchAllByColumn')
->with('select n.id_notice from notices as n inner join exemplaires as e on (n.id_notice=e.id_notice and n.type=1 and n.type_doc="8") inner join cms_article as a on a.id_article=e.id_origine or a.parent_id = e.id_origine where (a.debut is not null and date(a.debut) > date(now())) or (a.fin is not null and date(a.fin) < date(now()))')
->answers([])
->whenCalled('fetchAllByColumn')
->with('select id_article from cms_article where id_article not in (select a.id_article from notices as n inner join exemplaires as e on (n.id_notice=e.id_notice and n.type=1 and n.type_doc="8") inner join cms_article as a on a.id_article=e.id_origine) and ((debut is null or date(debut) <= date(now())) and (fin is null or date(fin) >= date(now()))) and status=3 and indexation=1')
->answers([22299])
->whenCalled('fetchAllByColumn')
->with('select id_article from cms_article where id_article not in (select a.id_article from notices as n inner join exemplaires as e on (n.id_notice=e.id_notice and n.type=1 and n.type_doc="8") inner join cms_article as a on a.id_article=e.id_origine or a.parent_id = e.id_origine) and ((debut is null or date(debut) <= date(now())) and (fin is null or date(fin) >= date(now()))) and status=3 and indexation=1')
->answers([])
->beStrict());
$this->fixture(Class_Notice::class,
['id' => 159,
'titres' => 'ARTICLE EN FRANCAIS',
'type' => 1,
'type_doc' => 8]);
$this->fixture(Class_Exemplaire::class,
['id' => 753,
'id_notice' => 159,
'id_origine' => 22199]);
$this->fixture(Class_Article::class,
['id' => 22199,
'id_notice' => 159,
'parent_id' => 0,
'titre' => 'Article en francais',
'contenu' => 'Article en francais',
'status' => Class_Article::STATUS_VALIDATED,
'indexation' => 1,
'langue' => 'fr']);
$this->fixture(Class_Article::class,
['id' => 22299,
'titre' => 'translation article',
'contenu' => 'translation article',
'status' => Class_Article::STATUS_VALIDATED,
'indexation' => 1,
'langue' => 'en',
'parent_id' => 22199]);
$this->_phase = $this->_buildPhase('PseudoRecordCms')
->setMemoryCleaner(function(): void {})
->run();
}
/** @test */
public function recordShouldHaveTitresTestingArticle() {
$this->assertEquals('ARTICLE EN FRANCAIS', Class_Notice::findFirstBy(['type_doc' => Class_TypeDoc::ARTICLE])
->getTitres());
}
}
class PhasePseudoRecordCmsDeleteOrphanRecordsTest
extends Class_Cosmogramme_Integration_PhaseTestCase
{
protected array $_storm_scopes = [PhasePseudoRecordCmsDeleteOrphanRecordsTest::class];
protected function _getPreviousPhase()
{
return (new Class_Cosmogramme_Integration_Phase(0.1))->beCron();
......@@ -264,10 +198,10 @@ class PhasePseudoRecordCmsDeleteOrphanRecordsTest
{
parent::setUp();
Zend_Registry::set('sql',
$this->mock()
->whenCalled('fetchAllByColumn')
->answers([]));
Class_Cosmogramme_Integration_PhasePseudoRecordCms::setTimeSource(new TimeSourceForTest('2022-04-30 14:00:00'));
Class_AdminVar::set('WORKFLOW', 1);
Class_AdminVar::set('DEFAULT_LANGUAGE', 'en');
Class_AdminVar::set('LANGUES', 'en;');
$this->fixture(Class_Notice::class,
['id' => 21,
......@@ -315,10 +249,10 @@ class PhasePseudoRecordCmsDeleteOrphanRecordsTest
}
/** @test */
public function sqlShouldExists()
public function tearDown(): void
{
$this->assertSql("SELECT `notices`.* FROM `notices` WHERE (`notices`.`type_doc` = 8 AND `notices`.`type` = 1 AND NOT EXISTS (SELECT 'x' FROM `cms_article` AS `articles` WHERE (`notices`.`id_notice` = `articles`.`id_notice`))) LIMIT 100");
Class_Cosmogramme_Integration_PhasePseudoRecordCms::setTimeSource(null);
parent::tearDown();
}
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment