Jonaskeutel has uploaded a new change for review. https://gerrit.wikimedia.org/r/209211
Change subject: Special pages now contain the lines they used to inherit ...................................................................... Special pages now contain the lines they used to inherit Change-Id: I601c334f8faaa4651db6f21ba6f1ebaba4eeb27f --- M WikidataQualityExternalValidation.php A modules/SpecialExternalValidationPage.css M specials/SpecialCrossCheck.php M specials/SpecialExternalDbs.php 4 files changed, 589 insertions(+), 12 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/WikidataQualityExternalValidation refs/changes/11/209211/1 diff --git a/WikidataQualityExternalValidation.php b/WikidataQualityExternalValidation.php index e9caee9..12b5426 100644 --- a/WikidataQualityExternalValidation.php +++ b/WikidataQualityExternalValidation.php @@ -41,6 +41,13 @@ // Define API modules $GLOBALS['wgAPIModules']['wdqacrosscheck'] = 'WikidataQuality\ExternalValidation\Api\CrossCheck'; + // Define modules + $GLOBALS['wgResourceModules']['SpecialExternalValidationPage'] = array ( + 'styles' => '/modules/SpecialExternalValidationPage.css', + 'localBasePath' => __DIR__, + 'remoteExtPath' => 'WikidataQualityExternalValidation' + ); + // Define database table names define( 'DUMP_DATA_TABLE', 'wdqa_external_data' ); define( 'DUMP_META_TABLE', 'wdqa_dump_information' ); diff --git a/modules/SpecialExternalValidationPage.css b/modules/SpecialExternalValidationPage.css new file mode 100644 index 0000000..d628c9c --- /dev/null +++ b/modules/SpecialExternalValidationPage.css @@ -0,0 +1,83 @@ +/* Entity id form */ +.wbq-checkresult-form { + margin-top: 10px; + margin-bottom: 20px; +} + +.wbq-checkresult-form-entity-id { + width: 120px; +} + +.wbq-checkresult-form-submit { + margin-left: 5px; +} + +/* Notices */ +.wbq-checkresult-notice { + font-style: italic; +} + +.wbq-checkresult-notice-error { + font-weight: bold; + color: #BA0000; +} + +/* Statuses */ +.wbq-status { + font-weight: bold; +} + +.wbq-status-success { + color: #008000; +} + +.wbq-status-partial-success { + color: #6CB500; +} + +.wbq-status-warning { + color: #E6B800; +} + +.wbq-status-error { + color: #BA0000; +} + +.wbq-status-unknown { + color: #404040; +} + +/* Tooltip */ +.wbq-tooltip-indicator:before { + content: '[?]'; + color: #CCC; + font-weight: 600; +} + +[tooltip]:before { + /* needed - do not touch */ + content: attr(tooltip); + position: absolute; + opacity: 0; + + /* customizable */ + transition: all 0.20s ease; + padding: 5px; + border: 1px solid #AAA; + border-radius: 5px; + box-shadow: 2px 2px 1px #CCC; +} + +[tooltip]:hover:before { + /* needed - do not touch */ + opacity: 1; + + /* customizable */ + background: #F2F2F2; + margin-top: -35px; + margin-left: -5px; +} + +[tooltip]:not([tooltip-persistent]):before { + pointer-events: none; +} \ No newline at end of file diff --git a/specials/SpecialCrossCheck.php b/specials/SpecialCrossCheck.php index ccd4ec0..12f6bc5 100755 --- a/specials/SpecialCrossCheck.php +++ b/specials/SpecialCrossCheck.php @@ -2,13 +2,28 @@ namespace WikidataQuality\ExternalValidation\Specials; +use SpecialPage; +use ValueFormatters\FormatterOptions; +use Wikibase\Lib\EntityIdLabelFormatter; +use Wikibase\Lib\EntityIdHtmlLinkFormatter; +use Wikibase\Lib\EntityIdLinkFormatter; +use Wikibase\Lib\HtmlUrlFormatter; +use Wikibase\Lib\LanguageNameLookup; +use Wikibase\Lib\SnakFormatter; +use Wikibase\Lib\Store\LanguageLabelDescriptionLookup; +use Wikibase\Repo\WikibaseRepo; use Html; use JobQueueGroup; use Linker; use Traversable; use Wikibase\DataModel\Entity\Entity; use Wikibase\DataModel\Entity\EntityId; -use Wikibase\Repo\WikibaseRepo; +use DataValues\DataValue; +use Doctrine\Instantiator\Exception\InvalidArgumentException; +use Doctrine\Instantiator\Exception\UnexpectedValueException; +use Wikibase\DataModel\Entity\EntityIdParsingException; +use Countable; +use Wikibase\DataModel\Entity\EntityIdValue; use WikidataQuality\ExternalValidation\CheckForCrossCheckViolationsJob; use WikidataQuality\ExternalValidation\CrossCheck\Comparer\DataValueComparerFactory; use WikidataQuality\ExternalValidation\CrossCheck\CrossChecker; @@ -18,18 +33,88 @@ use WikidataQuality\ExternalValidation\DumpMetaInformation\DumpMetaInformationLookup; use WikidataQuality\Html\HtmlTable; use WikidataQuality\Html\HtmlTableHeader; -use WikidataQuality\Specials\SpecialCheckResultPage; -class SpecialCrossCheck extends SpecialCheckResultPage { +class SpecialCrossCheck extends SpecialPage { /** * @var CrossCheckInteractor */ private $crossCheckInteractor; - public function __construct() { - parent::__construct( 'CrossCheck' ); + /** + * @var \Wikibase\DataModel\Entity\EntityIdParser + */ + protected $entityIdParser; + + /** + * @var \Wikibase\Lib\Store\EntityLookup + */ + protected $entityLookup; + + /** + * @var \ValueFormatters\ValueFormatter + */ + protected $dataValueFormatter; + + /** + * @var EntityIdLabelFormatter + */ + protected $entityIdLabelFormatter; + + /** + * @var EntityIdLinkFormatter + */ + protected $entityIdLinkFormatter; + + /** + * @var EntityIdHtmlLinkFormatter + */ + protected $entityIdHtmlLinkFormatter; + + /** + * @var HtmlUrlFormatter + */ + protected $htmlUrlFormatter; + + /** + * @param string $name + * @param string $restriction + * @param bool $listed + * @param bool $function + * @param string $file + * @param bool $includable + */ + public function __construct( $name = 'CrossCheck', $restriction = '', $listed = true, $function = false, $file = '', $includable = false ) { + parent::__construct( $name, $restriction, $listed, $function, $file, $includable ); + + $repo = WikibaseRepo::getDefaultInstance(); + + // Get entity lookup + $this->entityLookup = $repo->getEntityLookup(); + + // Get entity id parser + $this->entityIdParser = $repo->getEntityIdParser(); + + // Get value formatter + $formatterOptions = new FormatterOptions(); + $formatterOptions->setOption( SnakFormatter::OPT_LANG, $this->getLanguage()->getCode() ); + $this->dataValueFormatter = $repo->getValueFormatterFactory()->getValueFormatter( SnakFormatter::FORMAT_HTML, $formatterOptions ); + + // Get entity id link formatters + $entityTitleLookup = $repo->getEntityTitleLookup(); + $labelLookup = new LanguageLabelDescriptionLookup( $repo->getTermLookup(), $this->getLanguage()->getCode() ); + $this->entityIdLabelFormatter = new EntityIdLabelFormatter( $labelLookup ); + $this->entityIdLinkFormatter = new EntityIdLinkFormatter( $entityTitleLookup ); + $this->entityIdHtmlLinkFormatter = new EntityIdHtmlLinkFormatter( + $labelLookup, + $entityTitleLookup, + new LanguageNameLookup() + ); + + // Get url formatter + $formatterOptions = new FormatterOptions(); + $this->htmlUrlFormatter = new HtmlUrlFormatter( $formatterOptions ); $claimGuidParser = WikibaseRepo::getDefaultInstance()->getClaimGuidParser(); $dataValueComparerFactory = new DataValueComparerFactory( $this->entityLookup ); @@ -43,6 +128,25 @@ $claimGuidParser, $crossChecker ); + } + + /** + * Returns array of modules that should be added + * + * @return array + */ + protected function getModules() { + return array ( 'SpecialExternalValidationPage' ); + } + + + /** + * @see SpecialPage::getGroupName + * + * @return string + */ + function getGroupName() { + return 'wikidataquality'; } /** @@ -89,6 +193,302 @@ $this->doEvaluation( $entity, $results ); return $results; + } + + /** + * @see SpecialPage::execute + * + * @param string|null $subPage + */ + public function execute( $subPage ) { + $out = $this->getOutput(); + + $postRequest = $this->getContext()->getRequest()->getVal( 'entityId' ); + if ( $postRequest ) { + $out->redirect( $this->getPageTitle( strtoupper( $postRequest ) )->getLocalURL() ); + return; + } + + $out->addModules( $this->getModules() ); + + $this->setHeaders(); + + $out->addHTML( + $this->getInstructionsText() + . $this->buildEntityIdForm() + ); + + if ( !$subPage ) { + return; + } + + if ( !is_string( $subPage ) ) { + throw new InvalidArgumentException( '$subPage must be string.' ); + } + + try { + $entityId = $this->entityIdParser->parse( $subPage ); + $entity = $this->entityLookup->getEntity( $entityId ); + } catch ( EntityIdParsingException $e ) { + $out->addHTML( + $this->buildNotice( $this->msg( 'wikidataquality-checkresult-invalid-entity-id' )->text(), true ) + ); + return; + } + + if ( !$entity ) { + $out->addHTML( + $this->buildNotice( $this->msg( 'wikidataquality-checkresult-not-existent-entity' )->text(), true ) + ); + return; + } + + $results = $this->executeCheck( $entity ); + + if ( !is_array( $results ) ) { + if ( !( $results instanceof Traversable && $results instanceof Countable ) ) { + throw new UnexpectedValueException( 'SpecialCheckResultPage::executeCheck has to return an array or traversable and countable object.' ); + } + } + + if ( $results && count( $results ) > 0 ) { + $out->addHTML( + $this->buildResultHeader( $entityId ) + . $this->buildSummary( $results ) + . $this->buildResultTable( $entityId, $results ) + ); + } else { + $out->addHTML( + $this->buildResultHeader( $entityId ) + . $this->buildNotice( $this->getEmptyResultText() ) + ); + } + } + + /** + * Returns html text of the entity id form + * + * @return string + */ + protected function buildEntityIdForm() { + return + Html::openElement( + 'form', + array ( + 'class' => 'wbq-checkresult-form', + 'action' => $this->getPageTitle()->getLocalURL(), + 'method' => 'post' + ) + ) + . Html::input( + 'entityId', + '', + 'text', + array ( + 'class' => 'wbq-checkresult-form-entity-id', + 'placeholder' => $this->msg( 'wikidataquality-checkresult-form-entityid-placeholder' )->text() + ) + ) + . Html::input( + 'submit', + $this->msg( 'wikidataquality-checkresult-form-submit-label' )->text(), + 'submit', + array ( + 'class' => 'wbq-checkresult-form-submit' + ) + ) + . Html::closeElement( 'form' ); + } + + /** + * Builds notice with given message. Optionally notice can be handles as error by settings $error to true + * + * @param string $message + * @param bool $error + * + * @return string + */ + protected function buildNotice( $message, $error = false ) { + if ( !is_string( $message ) ) { + throw new InvalidArgumentException( '$message must be string.' ); + } + if ( !is_bool( $error ) ) { + throw new InvalidArgumentException( '$error must be bool.' ); + } + + $cssClasses = 'wbq-checkresult-notice'; + if ( $error ) { + $cssClasses .= ' wbq-checkresult-notice-error'; + } + + return + Html::element( + 'p', + array ( + 'class' => $cssClasses + ), + $message ); + } + + /** + * Returns html text of the result header + * + * @param EntityId $entityId + * + * @return string + */ + protected function buildResultHeader( EntityId $entityId ) { + $entityLink = sprintf( '%s (%s)', + $this->entityIdHtmlLinkFormatter->formatEntityId( $entityId ), + $entityId->getSerialization() ); + + return + Html::openElement( 'h3' ) + . $this->msg( 'wikidataquality-checkresult-result-headline', $entityLink )->text() + . Html::closeElement( 'h3' ); + } + + /** + * Builds summary from given results + * + * @param array|Traversable $results + * + * @return string + */ + protected function buildSummary( $results ) { + $statuses = array (); + foreach ( $results as $result ) { + $status = strtolower( $result->getStatus() ); + if ( array_key_exists( $status, $statuses ) ) { + $statuses[ $status ]++; + } else { + $statuses[ $status ] = 1; + } + } + + $statusElements = array (); + foreach ( $statuses as $status => $count ) { + if ( $count > 0 ) { + $statusElements[ ] = + $this->formatStatus( $status ) + . ": " + . $count; + } + } + $summary = + Html::openElement( 'p' ) + . implode( ', ', $statusElements ) + . Html::closeElement( 'p' ); + + return $summary; + } + + /** + * Builds a html div element with given content and a tooltip with given tooltip content + * If $tooltipContent is null, no tooltip will be created + * + * @param string $content + * @param string $tooltipContent + * + * @return string + */ + protected function buildTooltipElement( $content, $tooltipContent ) { + if ( !is_string( $content ) ) { + throw new InvalidArgumentException( '$content has to be string.' ); + } + if ( $tooltipContent && ( !is_string( $tooltipContent ) ) ) { + throw new InvalidArgumentException( '$tooltipContent, if provided, has to be string.' ); + } + + if ( empty( $tooltipContent ) ) { + return $content; + } + + $tooltipIndicator = Html::element( + 'span', + array ( + 'class' => 'wbq-tooltip-indicator' + ) + ); + + return + Html::openElement( + 'span', + array ( + 'tooltip' => $tooltipContent + ) + ) + . sprintf( '%s %s', $content, $tooltipIndicator ) + . Html::closeElement( 'span' ); + } + + /** + * Formats given status to html + * + * @param string $status + * + * @return string + */ + protected function formatStatus( $status ) { + if ( !is_string( $status ) ) { + throw new InvalidArgumentException( '$status has to be string.' ); + } + + $messageName = "wikidataquality-checkresult-status-" . strtolower( $status ); + $message = $this->msg( $messageName )->text(); + + $statusMapping = $this->getStatusMapping(); + if ( array_key_exists( $status, $statusMapping ) ) { + $genericStatus = $statusMapping[ $status ]; + } else { + $genericStatus = 'unknown'; + } + + $formattedStatus = + Html::element( + 'span', + array ( + 'class' => 'wbq-status wbq-status-' . $genericStatus + ), + $message + ); + + return $formattedStatus; + } + + /** + * Parses data values to human-readable string + * + * @param DataValue|array $dataValues + * @param bool $linking + * @param string $separator + * + * @return string + */ + protected function formatDataValues( $dataValues, $linking = true, $separator = ', ' ) { + if ( $dataValues instanceof DataValue ) { + $dataValues = array ( $dataValues ); + } elseif ( !is_array( $dataValues ) ) { + throw new InvalidArgumentException( '$dataValues has to be instance of DataValue or an array of DataValues.' ); + } + + $formattedDataValues = array (); + foreach ( $dataValues as $dataValue ) { + if ( !( $dataValue instanceof DataValue ) ) { + throw new InvalidArgumentException( '$dataValues has to be instance of DataValue or an array of DataValues.' ); + } + if ( $dataValue instanceof EntityIdValue ) { + if ( $linking ) { + $formattedDataValues[ ] = $this->entityIdHtmlLinkFormatter->formatEntityId( $dataValue->getEntityId() ); + } else { + $formattedDataValues[ ] = $this->entityIdLabelFormatter->formatEntityId( $dataValue->getEntityId() ); + } + } else { + $formattedDataValues[ ] = $this->dataValueFormatter->format( $dataValue ); + } + } + + return implode( $separator, $formattedDataValues ); } /** @@ -156,14 +556,11 @@ } protected function doEvaluation( $entity, $results ) { - //TODO: Push (deferred) job(s) in queue $checkTimeStamp = wfTimestamp( TS_MW ); $jobs = array(); $jobs[] = CheckForCrossCheckViolationsJob::newInsertNow( $entity, $checkTimeStamp, $results ); $jobs[] = CheckForCrossCheckViolationsJob::newInsertDeferred( $entity, $checkTimeStamp, 10 ); - $jobs[0]->run(); - $jobs[1]->run(); JobQueueGroup::singleton()->push( $jobs ); } } diff --git a/specials/SpecialExternalDbs.php b/specials/SpecialExternalDbs.php index 36ab57a..488726a 100755 --- a/specials/SpecialExternalDbs.php +++ b/specials/SpecialExternalDbs.php @@ -2,6 +2,17 @@ namespace WikidataQuality\ExternalValidation\Specials; + +use SpecialPage; +use ValueFormatters\FormatterOptions; +use Wikibase\Lib\EntityIdLabelFormatter; +use Wikibase\Lib\EntityIdHtmlLinkFormatter; +use Wikibase\Lib\EntityIdLinkFormatter; +use Wikibase\Lib\HtmlUrlFormatter; +use Wikibase\Lib\LanguageNameLookup; +use Wikibase\Lib\SnakFormatter; +use Wikibase\Lib\Store\LanguageLabelDescriptionLookup; +use Wikibase\Repo\WikibaseRepo; use DateInterval; use DateTimeZone; use Html; @@ -11,14 +22,93 @@ use WikidataQuality\ExternalValidation\DumpMetaInformation\DumpMetaInformationLookup; use WikidataQuality\Html\HtmlTable; use WikidataQuality\Html\HtmlTableCell; -use WikidataQuality\Specials\SpecialWikidataQualityPage; use DateTime; -class SpecialExternalDbs extends SpecialWikidataQualityPage { +class SpecialExternalDbs extends SpecialPage { - public function __construct() { - parent::__construct( 'ExternalDbs' ); + /** + * @var \Wikibase\DataModel\Entity\EntityIdParser + */ + protected $entityIdParser; + + /** + * @var \Wikibase\Lib\Store\EntityLookup + */ + protected $entityLookup; + + /** + * @var \ValueFormatters\ValueFormatter + */ + protected $dataValueFormatter; + + /** + * @var EntityIdLabelFormatter + */ + protected $entityIdLabelFormatter; + + /** + * @var EntityIdLinkFormatter + */ + protected $entityIdLinkFormatter; + + /** + * @var EntityIdHtmlLinkFormatter + */ + protected $entityIdHtmlLinkFormatter; + + /** + * @var HtmlUrlFormatter + */ + protected $htmlUrlFormatter; + + /** + * @param string $name + * @param string $restriction + * @param bool $listed + * @param bool $function + * @param string $file + * @param bool $includable + */ + public function __construct( $name = 'ExternalDbs', $restriction = '', $listed = true, $function = false, $file = '', $includable = false ) { + parent::__construct( $name, $restriction, $listed, $function, $file, $includable ); + + $repo = WikibaseRepo::getDefaultInstance(); + + // Get entity lookup + $this->entityLookup = $repo->getEntityLookup(); + + // Get entity id parser + $this->entityIdParser = $repo->getEntityIdParser(); + + // Get value formatter + $formatterOptions = new FormatterOptions(); + $formatterOptions->setOption( SnakFormatter::OPT_LANG, $this->getLanguage()->getCode() ); + $this->dataValueFormatter = $repo->getValueFormatterFactory()->getValueFormatter( SnakFormatter::FORMAT_HTML, $formatterOptions ); + + // Get entity id link formatters + $entityTitleLookup = $repo->getEntityTitleLookup(); + $labelLookup = new LanguageLabelDescriptionLookup( $repo->getTermLookup(), $this->getLanguage()->getCode() ); + $this->entityIdLabelFormatter = new EntityIdLabelFormatter( $labelLookup ); + $this->entityIdLinkFormatter = new EntityIdLinkFormatter( $entityTitleLookup ); + $this->entityIdHtmlLinkFormatter = new EntityIdHtmlLinkFormatter( + $labelLookup, + $entityTitleLookup, + new LanguageNameLookup() + ); + + // Get url formatter + $formatterOptions = new FormatterOptions(); + $this->htmlUrlFormatter = new HtmlUrlFormatter( $formatterOptions ); + } + + /** + * @see SpecialPage::getGroupName + * + * @return string + */ + function getGroupName() { + return 'wikidataquality'; } /** -- To view, visit https://gerrit.wikimedia.org/r/209211 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I601c334f8faaa4651db6f21ba6f1ebaba4eeb27f Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/WikidataQualityExternalValidation Gerrit-Branch: v1 Gerrit-Owner: Jonaskeutel <jonas.keu...@student.hpi.de> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits