jenkins-bot has submitted this change and it was merged.
Change subject: (bug 58342) Inject TermBox when viewing
......................................................................
(bug 58342) Inject TermBox when viewing
This introduces a generic mechanism for managing and expanding
placeholders in cached content, to allow user specific content
to be injected on demand.
This change also factors several bits of logic out of EntityView
and provides more fine grained tests.
Change-Id: I75cbbf01c644232f906a1d4f8e990b3fdbf781ec
---
M repo/Wikibase.classes.php
M repo/Wikibase.hooks.php
M repo/Wikibase.php
M repo/includes/EntityView.php
A repo/includes/EntityViewPlaceholderExpander.php
A repo/includes/UserLanguageLookup.php
A repo/includes/view/SectionEditLinkGenerator.php
A repo/includes/view/TermBoxView.php
A repo/includes/view/TextInjector.php
M repo/resources/themes/default/wikibase.toc.css
A repo/tests/phpunit/includes/EntityViewPlaceholderExpanderTest.php
M repo/tests/phpunit/includes/EntityViewTest.php
A repo/tests/phpunit/includes/UserLanguagesTest.php
A repo/tests/phpunit/includes/view/SectionEditLinkGeneratorTest.php
A repo/tests/phpunit/includes/view/TermBoxViewTest.php
A repo/tests/phpunit/includes/view/TextInjectorTest.php
16 files changed, 996 insertions(+), 171 deletions(-)
Approvals:
Aude: Looks good to me, approved
jenkins-bot: Verified
diff --git a/repo/Wikibase.classes.php b/repo/Wikibase.classes.php
index 99418fc..819c41a 100644
--- a/repo/Wikibase.classes.php
+++ b/repo/Wikibase.classes.php
@@ -41,6 +41,7 @@
'Wikibase\ItemContentDiffView' =>
'includes/ItemContentDiffView.php',
'Wikibase\ItemDisambiguation' =>
'includes/ItemDisambiguation.php',
'Wikibase\EntityView' => 'includes/EntityView.php',
+ 'Wikibase\EntityViewPlaceholderExpander' =>
'includes/EntityViewPlaceholderExpander.php',
'Wikibase\ExceptionWithCode' =>
'includes/ExceptionWithCode.php',
'Wikibase\ItemView' => 'includes/ItemView.php',
'Wikibase\LabelDescriptionDuplicateDetector' =>
'includes/LabelDescriptionDuplicateDetector.php',
@@ -52,6 +53,12 @@
'Wikibase\SummaryFormatter' => 'includes/SummaryFormatter.php',
'Wikibase\Repo\WikibaseRepo' => 'includes/WikibaseRepo.php',
'Wikibase\ClaimHtmlGenerator' =>
'includes/ClaimHtmlGenerator.php',
+ 'Wikibase\UserLanguageLookup' =>
'includes/UserLanguageLookup.php',
+
+ // includes/view
+ 'Wikibase\SectionEditLinkGenerator' =>
'includes/view/SectionEditLinkGenerator.php',
+ 'Wikibase\TermBoxView' => 'includes/view/TermBoxView.php',
+ 'Wikibase\TextInjector' => 'includes/view/TextInjector.php',
// includes/ChangeOp
'Wikibase\ChangeOp\ChangeOps' =>
'includes/ChangeOp/ChangeOps.php',
diff --git a/repo/Wikibase.hooks.php b/repo/Wikibase.hooks.php
index 77ec924..7bed6a6 100644
--- a/repo/Wikibase.hooks.php
+++ b/repo/Wikibase.hooks.php
@@ -16,6 +16,7 @@
use MWContentSerializationException;
use MWException;
use OutputPage;
+use ParserOutput;
use RecentChange;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
@@ -1031,4 +1032,54 @@
}
return true;
}
+
+ /**
+ * Called when pushing meta-info from the ParserOutput into OutputPage.
+ * Used to transfer the 'wb-placeholders' from ParserOutput to
OutputPage.
+ *
+ * @param OutputPage $out
+ * @param ParserOutput $parserOutput
+ *
+ * @return bool
+ */
+ public static function onOutputPageParserOutput( OutputPage $out,
ParserOutput $parserOutput ) {
+ $placeholders = $parserOutput->getExtensionData(
'wikibase-view-chunks' );
+
+ if ( $placeholders ) {
+ $out->setProperty( 'wikibase-view-chunks',
$placeholders );
+ }
+
+ return true;
+ }
+
+ /**
+ * Called when pushing HTML from the ParserOutput into OutputPage.
+ * Used to expand any placeholders in the OutputPage's
'wb-placeholders' property
+ * in the HTML.
+ *
+ * @param OutputPage $out
+ * @param string &$html the HTML to mangle
+ *
+ * @return bool
+ */
+ public static function onOutputPageBeforeHTML( OutputPage $out, &$html
) {
+ $placeholders = $out->getProperty( 'wikibase-view-chunks' );
+
+ if ( $placeholders ) {
+ $injector = new TextInjector( $placeholders );
+
+ $expander = new EntityViewPlaceholderExpander(
+ $out->getTitle(),
+ $out->getUser(),
+ $out->getLanguage(),
+
WikibaseRepo::getDefaultInstance()->getEntityIdParser(),
+
WikibaseRepo::getDefaultInstance()->getEntityLookup(),
+ new UserLanguageLookup( $out->getUser() )
+ );
+
+ $html = $injector->inject( $html, array( $expander,
'getHtmlForPlaceholder' ) );
+ }
+
+ return true;
+ }
}
diff --git a/repo/Wikibase.php b/repo/Wikibase.php
index 5d78a4e..192b291 100644
--- a/repo/Wikibase.php
+++ b/repo/Wikibase.php
@@ -184,6 +184,8 @@
$wgHooks['TitleGetRestrictionTypes'][] =
'Wikibase\RepoHooks::onTitleGetRestrictionTypes';
$wgHooks['AbuseFilter-contentToString'][] =
'Wikibase\RepoHooks::onAbuseFilterContentToString';
$wgHooks['SpecialPage_reorderPages'][] =
'Wikibase\RepoHooks::onSpecialPage_reorderPages';
+ $wgHooks['OutputPageParserOutput'][] =
'Wikibase\RepoHooks::onOutputPageParserOutput';
+ $wgHooks['OutputPageBeforeHTML'][]
= 'Wikibase\RepoHooks::onOutputPageBeforeHTML';
// Resource Loader Modules:
$wgResourceModules = array_merge( $wgResourceModules, include( __DIR__
. "/resources/Resources.php" ) );
diff --git a/repo/includes/EntityView.php b/repo/includes/EntityView.php
index e51db47..b043946 100644
--- a/repo/includes/EntityView.php
+++ b/repo/includes/EntityView.php
@@ -59,6 +59,16 @@
protected $languageFallbackChain;
/**
+ * @var SectionEditLinkGenerator
+ */
+ protected $sectionEditLinkGenerator;
+
+ /**
+ * @var TextInjector
+ */
+ protected $injector;
+
+ /**
* Maps entity types to the corresponding entity view.
* FIXME: remove this stuff, big OCP violation
*
@@ -111,19 +121,46 @@
$this->entityTitleLookup = $entityTitleLookup;
$this->idParser = $idParser;
$this->languageFallbackChain = $languageFallbackChain;
+
+ $this->sectionEditLinkGenerator = new
SectionEditLinkGenerator();
+ $this->injector = new TextInjector();
+ }
+
+ /**
+ * Resets the placeholders managed by this view
+ */
+ public function resetPlaceholders() {
+ $this->injector = new TextInjector();
+ }
+
+ /**
+ * Returns the placeholder map build while generating HTML.
+ * The map returned here may be used with TextInjector.
+ *
+ * @return array string -> array
+ */
+ public function getPlaceholders() {
+ return $this->injector->getMarkers();
}
/**
* Builds and returns the HTML representing a whole WikibaseEntity.
*
+ * @note: The HTML returned by this method may contain placeholders.
Such placeholders can be
+ * expanded with the help of TextInjector::inject() calling back to
+ * EntityViewPlaceholderExpander::getExtraUserLanguages()
+ * @note: In order to keep the list of placeholders small, this calls
resetPlaceholders().
+ *
* @since 0.1
*
* @param EntityRevision $entityRevision the entity to render
* @param bool $editable whether editing is allowed (enabled edit links)
- * @return string
+ * @return string HTML
*/
public function getHtml( EntityRevision $entityRevision, $editable =
true ) {
wfProfileIn( __METHOD__ );
+
+ $this->resetPlaceholders();
//NOTE: even though $editable is unused at the moment, we will
need it for the JS-less editing model.
@@ -195,7 +232,14 @@
$claims = $this->getHtmlForClaims(
$entityRevision->getEntity(), $editable );
}
- $languageTerms = $this->getHtmlForLanguageTerms(
$entityRevision->getEntity(), $editable );
+ if ( $entityRevision->getEntity()->getId() ) {
+ // Placeholder for a termbox for the present item.
+ // EntityViewPlaceholderExpander must know about the
parameters used here.
+ $languageTerms = $this->injector->newMarker( 'termbox',
$entityRevision->getEntity()->getId()->getSerialization() );
+ } else {
+ //NOTE: this should only happen during testing
+ $languageTerms = '';
+ }
$html = wfTemplate( 'wb-entity-content',
$this->getHtmlForLabel( $entityRevision->getEntity(),
$editable ),
@@ -219,9 +263,9 @@
$tocContent = '';
$tocSections = $this->getTocSections();
- if( empty( $tocSections ) ) {
- return '';
- }
+ // Placeholder for the TOC entry for the term box (which may or
may not be used for a given user).
+ // EntityViewPlaceholderExpander must know about the
'termbox-toc' name.
+ $tocContent .= $this->injector->newMarker( 'termbox-toc' );
$i = 1;
@@ -247,13 +291,7 @@
* @return array( link target => system message key )
*/
protected function getTocSections() {
- $lang = $this->getLanguage();
-
- if( !is_null( $lang ) && count( $this->getExtraUserLanguages(
$lang, $this->getUser() ) ) > 0 ) {
- return array( 'wb-terms' => 'wikibase-terms' );
- } else {
- return array();
- }
+ return array();
}
/**
@@ -294,6 +332,7 @@
if ( $generateHtml ) {
$html = $this->getHtml( $entityRevision,
$this->getLanguage(), $editable );
$pout->setText( $html );
+ $pout->setExtensionData( 'wikibase-view-chunks',
$this->getPlaceholders() );
}
//@todo: record sitelinks as iwlinks
@@ -425,97 +464,6 @@
}
/**
- * Selects the languages for the terms to display on first try, based
on the current user and
- * the available languages.
- *
- * @since 0.4
- *
- * @param Language $lang
- * @param User $user
- * @return string[] Selected language codes
- */
- private function getExtraUserLanguages( Language $lang , User $user ) {
- wfProfileIn( __METHOD__ );
- $result = array();
-
- // if the Babel extension is installed, add all languages of
the user
- if ( class_exists( 'Babel' ) && ( ! $user->isAnon() ) ) {
- $result = \Babel::getUserLanguages( $user );
- if( $lang !== null ) {
- $result = array_diff( $result, array(
$lang->getCode() ) );
- }
- }
- wfProfileOut( __METHOD__ );
- return $result;
- }
-
- /**
- * Builds and returns the HTML representing a WikibaseEntity's
collection of terms.
- *
- * @since 0.4
- *
- * @param Entity $entity the entity to render
- * @param bool $editable whether editing is allowed (enabled edit links)
- * @return string
- */
- public function getHtmlForLanguageTerms( Entity $entity, $editable =
true ) {
- $languages = $this->getExtraUserLanguages(
$this->getLanguage(), $this->getUser() );
- if ( count ( $languages ) === 0 ) {
- return '';
- }
-
- wfProfileIn( __METHOD__ );
-
- $html = $thead = $tbody = '';
-
- $labels = $entity->getLabels();
- $descriptions = $entity->getDescriptions();
-
- $html .= wfTemplate( 'wb-terms-heading', wfMessage(
'wikibase-terms' ) );
-
- $specialLabelPage = \SpecialPageFactory::getPage( "SetLabel" );
- $specialDescriptionPage = \SpecialPageFactory::getPage(
"SetDescription" );
- $rowNumber = 0;
- foreach( $languages as $language ) {
-
- $label = array_key_exists( $language, $labels ) ?
$labels[$language] : false;
- $description = array_key_exists( $language,
$descriptions ) ? $descriptions[$language] : false;
-
- $alternatingClass = ( $rowNumber++ % 2 ) ? 'even' :
'uneven';
-
- $editLabelLink =
$specialLabelPage->getTitle()->getLocalURL()
- . '/' . $this->getFormattedIdForEntity( $entity
) . '/' . $language;
-
- // TODO: this if is here just until the SetDescription
special page exists and
- // can be removed then
- if ( $specialDescriptionPage !== null ) {
- $editDescriptionLink =
$specialDescriptionPage->getTitle()->getLocalURL()
- . '/' . $this->getFormattedIdForEntity(
$entity ) . '/' . $language;
- } else {
- $editDescriptionLink = '';
- }
-
- $tbody .= wfTemplate( 'wb-term',
- $language,
- $alternatingClass,
- htmlspecialchars( Utils::fetchLanguageName(
$language ) ),
- htmlspecialchars( $label !== false ? $label :
wfMessage( 'wikibase-label-empty' ) ),
- htmlspecialchars( $description !== false ?
$description : wfMessage( 'wikibase-description-empty' ) ),
- $this->getHtmlForEditSection( $editLabelLink ),
- $this->getHtmlForEditSection(
$editDescriptionLink ),
- $label !== false ? '' : 'wb-value-empty',
- $description !== false ? '' : 'wb-value-empty',
- $this->getTitle()->getLocalURL() . '?setlang='
. $language
- );
- }
-
- $html = $html . wfTemplate( 'wb-terms-table', $tbody );
-
- wfProfileOut( __METHOD__ );
- return $html;
- }
-
- /**
* Builds and returns the HTML representing a WikibaseEntity's claims.
*
* @since 0.2
@@ -607,34 +555,11 @@
*
* @return string
*/
- public function getHtmlForEditSection( $url = '', $tag = 'span',
$action = 'edit', $enabled = true ) {
- wfProfileIn( __METHOD__ );
-
+ protected function getHtmlForEditSection( $url, $tag = 'span', $action
= 'edit', $enabled = true ) {
$key = $action === 'add' ? 'wikibase-add' : 'wikibase-edit';
- $buttonLabel = $this->getContext()->msg( $key )->text();
+ $msg = $this->getContext()->msg( $key );
- $button = ( $enabled ) ?
- wfTemplate( 'wikibase-toolbarbutton',
- $buttonLabel,
- $url // todo: add link to special page for
non-JS editing
- ) :
- wfTemplate( 'wikibase-toolbarbutton-disabled',
- $buttonLabel
- );
-
- $html = wfTemplate( 'wb-editsection',
- $tag,
- wfTemplate( 'wikibase-toolbar',
- '',
- wfTemplate( 'wikibase-toolbareditgroup',
- '',
- wfTemplate( 'wikibase-toolbar', '',
$button )
- )
- )
- );
-
- wfProfileOut( __METHOD__ );
- return $html;
+ return $this->sectionEditLinkGenerator->getHtmlForEditSection(
$url, $msg, $tag, $enabled );
}
/**
diff --git a/repo/includes/EntityViewPlaceholderExpander.php
b/repo/includes/EntityViewPlaceholderExpander.php
new file mode 100644
index 0000000..3041e2f
--- /dev/null
+++ b/repo/includes/EntityViewPlaceholderExpander.php
@@ -0,0 +1,240 @@
+<?php
+
+namespace Wikibase;
+
+use Language;
+use MWException;
+use RuntimeException;
+use Title;
+use User;
+use Wikibase\DataModel\Entity\EntityIdParser;
+
+/**
+ * Utility for expanding the placeholders left in the HTML by EntityView.
+ *
+ * This is used to inject any non-cacheable information into the HTML
+ * that was cached as part of the ParserOutput.
+ *
+ * @note This class encapsulated knowledge about which placeholders are used by
+ * EntityView, and with what meaning.
+ *
+ * @see EntityView
+ *
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class EntityViewPlaceholderExpander {
+
+ /**
+ * @var User
+ */
+ protected $user;
+
+ /**
+ * @var Title
+ */
+ protected $targetPage;
+
+ /**
+ * The current user language
+ *
+ * @var Language
+ */
+ protected $userLanguage;
+
+ /**
+ * @var string[]|null
+ */
+ protected $extraLanguages = null;
+
+ /**
+ * @var EntityIdParser
+ */
+ protected $idParser;
+
+ /**
+ * @var EntityLookup
+ */
+ protected $entityLookup;
+
+ /**
+ * @var UserLanguageLookup
+ */
+ protected $userLanguageLookup;
+
+ /**
+ * @param Title $targetPage the page for which this expander is
supposed to handle expansion.
+ * @param User $user the current user
+ * @param Language $uiLanguage the user's current UI language (as per
the present request)
+ *
+ * @param Lib\EntityIdParser $idParser
+ * @param EntityLookup $entityLookup
+ * @param UserLanguageLookup|null $userLanguageLookup
+ */
+ public function __construct(
+ Title $targetPage,
+ User $user,
+ Language $uiLanguage,
+ EntityIdParser $idParser,
+ EntityLookup $entityLookup,
+ UserLanguageLookup $userLanguageLookup
+ ) {
+ $this->targetPage = $targetPage;
+ $this->user = $user;
+ $this->uiLanguage = $uiLanguage;
+
+ $this->entityLookup = $entityLookup;
+ $this->idParser = $idParser;
+ $this->userLanguageLookup = $userLanguageLookup;
+ }
+
+ /**
+ * Returns a list of languages desired by the user in addition to the
current interface language.
+ *
+ * @see UserLanguages
+ *
+ * @return string[]
+ */
+ public function getExtraUserLanguages() {
+ if ( $this->extraLanguages === null ) {
+ // ignore current interface language
+ $skip = array( $this->uiLanguage->getCode() );
+ $this->extraLanguages =
$this->userLanguageLookup->getUserLanguages( $this->user, $skip );
+ }
+
+ return $this->extraLanguages;
+ }
+
+ /**
+ * Callback for expanding placeholders to HTML,
+ * for use as a callback passed to with TextInjector::inject().
+ *
+ * @note This delegates to expandPlaceholder, which encapsulates
knowledge about
+ * the meaning of each placeholder name, as used by EntityView.
+ *
+ * @param string $name the name (or kind) of placeholder; determines
how the expansion is done.
+ * @param mixed ... additional arguments associated with the placeholder
+ *
+ * @return string HTML to be substituted for the placeholder in the
output.
+ */
+ public function getHtmlForPlaceholder( $name ) {
+ $args = func_get_args();
+ $name = array_shift( $args );
+
+ try {
+ $html = $this->expandPlaceholder( $name, $args );
+ return $html;
+ } catch ( MWException $ex ) {
+ wfWarn( "Expansion of $name failed: " . $ex );
+ } catch ( RuntimeException $ex ) {
+ wfWarn( "Expansion of $name failed: " . $ex );
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns an argument from a list, first checking whether it is
present and has the correct type.
+ *
+ * @param array $args the argument list
+ * @param int $index the index of the desired argument
+ * @param string $type the desired type of the argument
+ * @param string $message the message to use if the argument is missing
or has the wrong type
+ *
+ * @return mixed
+ * @throws \InvalidArgumentException If the argument is missing or has
the wrong type
+ */
+ protected function extractArgument( $args, $index, $type, $message ) {
+ // this should be the entity id, as per the call to
$injector->newMarker() in getInnerHtml
+ if ( !isset( $args[$index] ) || gettype( $args[$index] ) !==
$type ) {
+ throw new \InvalidArgumentException( $message );
+ }
+
+ return $args[$index];
+ }
+
+ /**
+ * Dispatch the expansion of placeholders based on the name.
+ *
+ * @note This encodes knowledge about which placeholders are used by
EntityView with what
+ * intended meaning.
+ *
+ * @param $name
+ * @param array $args
+ *
+ * @return string
+ */
+ protected function expandPlaceholder( $name, array $args ) {
+
+ switch ( $name ) {
+ case 'termbox':
+ $entityId = $this->extractArgument(
+ $args,
+ 0,
+ 'string',
+ 'The first argument must be an entity
ID encoded as a string' );
+
+ $entityId = $this->idParser->parse( $entityId );
+ return $this->renderTermBox( $entityId );
+
+ case 'termbox-toc':
+ return $this->renderTermBoxTocEntry();
+
+ default:
+ wfWarn( "Unknown placeholder: $name" );
+ return '(((' . htmlspecialchars( $name ) .
')))';
+ }
+ }
+
+ /**
+ * Generates HTML to be injected into the TOC as a link to the term box.
+ *
+ * @return string HTML
+ */
+ public function renderTermBoxTocEntry() {
+ $languages = $this->getExtraUserLanguages();
+
+ if ( !$languages ) {
+ return '';
+ }
+
+ $html = wfTemplate( 'wb-entity-toc-section',
+ 0, // section number, not really used, it seems
+ 'wb-terms',
+ wfMessage( 'wikibase-terms' )->inLanguage(
$this->uiLanguage )->text()
+ );
+
+ return $html;
+ }
+
+ /**
+ * Generates HTML of the term box, to be injected into the entity page.
+ *
+ * @param Entityid $entityId
+ *
+ * @throws \InvalidArgumentException
+ * @return string HTML
+ */
+ public function renderTermBox( EntityId $entityId ) {
+ $languages = $this->getExtraUserLanguages();
+
+ if ( !$languages ) {
+ return '';
+ }
+
+ // we may want to cache this...
+ $entity = $this->entityLookup->getEntity( $entityId );
+
+ if ( !$entity ) {
+ return '';
+ }
+
+ $termBoxView = new TermBoxView( $this->uiLanguage );
+ $html = $termBoxView->renderTermBox( $this->targetPage,
$entity, $languages );
+
+ return $html;
+ }
+
+}
diff --git a/repo/includes/UserLanguageLookup.php
b/repo/includes/UserLanguageLookup.php
new file mode 100644
index 0000000..3978b4b
--- /dev/null
+++ b/repo/includes/UserLanguageLookup.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Wikibase;
+
+use User;
+
+/**
+ * Service for looking up the languages understood by a user.
+ *
+ * The current implementation relies on the Babel extension, but
+ * that may change.
+ *
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class UserLanguageLookup {
+
+ /**
+ * Returns the languages desired by the user, in order of preference.
+ *
+ * @param User $user
+ * @param array $skip a list of language codes to skip.
+ *
+ * @return string[] a list of language codes
+ */
+ public function getUserLanguages( User $user, $skip = array() ) {
+ wfProfileIn( __METHOD__ );
+
+ $languages = array();
+
+ // start with the user's UI language
+ $userLanguage = $user->getOption( 'language' );
+
+ if ( $userLanguage !== null ) {
+ $languages[] = $userLanguage;
+ }
+
+ // if the Babel extension is installed, grab the languages from
the user's babel box
+ if ( class_exists( 'Babel' ) && ( !$user->isAnon() ) ) {
+ $languages = array_merge( $languages,
\Babel::getUserLanguages( $user ) );
+ }
+
+ $languages = array_diff( $languages, $skip );
+ $languages = array_unique( $languages );
+
+ wfProfileOut( __METHOD__ );
+ return $languages;
+ }
+
+}
diff --git a/repo/includes/view/SectionEditLinkGenerator.php
b/repo/includes/view/SectionEditLinkGenerator.php
new file mode 100644
index 0000000..8a090c6
--- /dev/null
+++ b/repo/includes/view/SectionEditLinkGenerator.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Wikibase;
+
+use Message;
+
+/**
+ * Generates HTML for a section edit link
+ *
+ * @since 0.5
+ * @licence GNU GPL v2+
+ *
+ * @author Henning Snater
+ * @author Daniel Werner
+ * @author Daniel Kinzler
+ */
+class SectionEditLinkGenerator {
+
+
+ /**
+ * Returns a toolbar with an edit link for a single statement.
Equivalent to edit toolbar in JavaScript but with
+ * an edit link pointing to a special page where the statement can be
edited. In case JavaScript is available, this
+ * toolbar will be removed an replaced with the interactive JavaScript
one.
+ *
+ * @since 0.2
+ *
+ * @param string $url specifies the URL for the button, default is an
empty string
+ * @param Message $message the message to show on the link
+ * @param string $tag allows to specify the type of the outer node
+ * @param bool $enabled can be set to false to display the button
disabled
+ *
+ * @return string
+ */
+ public function getHtmlForEditSection( $url, Message $message, $tag =
'span', $enabled = true ) {
+ wfProfileIn( __METHOD__ );
+
+ $buttonLabel = $message->text();
+
+ $button = ( $enabled ) ?
+ wfTemplate( 'wikibase-toolbarbutton',
+ $buttonLabel,
+ $url // todo: add link to special page for
non-JS editing
+ ) :
+ wfTemplate( 'wikibase-toolbarbutton-disabled',
+ $buttonLabel
+ );
+
+ $html = wfTemplate( 'wb-editsection',
+ $tag,
+ wfTemplate( 'wikibase-toolbar',
+ '',
+ wfTemplate( 'wikibase-toolbareditgroup',
+ '',
+ wfTemplate( 'wikibase-toolbar', '',
$button )
+ )
+ )
+ );
+
+ wfProfileOut( __METHOD__ );
+ return $html;
+ }
+}
diff --git a/repo/includes/view/TermBoxView.php
b/repo/includes/view/TermBoxView.php
new file mode 100644
index 0000000..19f307e
--- /dev/null
+++ b/repo/includes/view/TermBoxView.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace Wikibase;
+
+use Language;
+use SpecialPage;
+use Title;
+
+
+/**
+ * Generates HTML for displaying the term box, that is, the box
+ * of labels and descriptions for additional languages a user understands.
+ *
+ * @since 0.5
+ * @licence GNU GPL v2+
+ *
+ * @author Daniel Kinzler
+ * @author Denny Vrandecic
+ */
+class TermBoxView {
+
+ /**
+ * @var SectionEditLinkGenerator
+ */
+ protected $sectionEditLinkGenerator;
+
+ /**
+ * @var Language
+ */
+ protected $language;
+
+ public function __construct( Language $language ) {
+ $this->language = $language;
+ $this->sectionEditLinkGenerator = new
SectionEditLinkGenerator();
+ }
+
+ /**
+ * @param $key
+ *
+ * @return \Message
+ */
+ protected function msg( $key ) {
+ return wfMessage( $key )->inLanguage( $this->language );
+ }
+
+ /**
+ * Builds and returns the HTML representing a WikibaseEntity's
collection of terms.
+ *
+ * @since 0.4
+ *
+ * @param Title $title The title of the page the term box is to be
shown on
+ * @param Entity $entity the entity to render
+ * @param string[] $languages list of languages to show terms for
+ * @param bool $editable whether editing is allowed (enabled edit links)
+ *
+ * @return string
+ */
+ public function renderTermBox( Title $title, Entity $entity,
$languages, $editable = true ) {
+ if ( empty( $languages ) ) {
+ return '';
+ }
+
+ wfProfileIn( __METHOD__ );
+
+ $html = $thead = $tbody = '';
+
+ $labels = $entity->getLabels();
+ $descriptions = $entity->getDescriptions();
+
+ $html .= wfTemplate( 'wb-terms-heading', $this->msg(
'wikibase-terms' ) );
+
+ $rowNumber = 0;
+ foreach( $languages as $language ) {
+
+ $label = array_key_exists( $language, $labels ) ?
$labels[$language] : false;
+ $description = array_key_exists( $language,
$descriptions ) ? $descriptions[$language] : false;
+
+ $alternatingClass = ( $rowNumber++ % 2 ) ? 'even' :
'uneven';
+
+ $entitySubPage = $this->getFormattedIdForEntity(
$entity ) . '/' . $language;
+ $specialSetLabel = SpecialPage::getTitleFor(
"SetLabel", $entitySubPage );
+ $specialSetDescription = SpecialPage::getTitleFor(
"SetDescription", $entitySubPage );
+
+ $editLabelLink = $specialSetLabel->getLocalURL();
+ $editDescriptionLink =
$specialSetDescription->getLocalURL();
+
+ $tbody .= wfTemplate( 'wb-term',
+ $language,
+ $alternatingClass,
+ htmlspecialchars( Utils::fetchLanguageName(
$language ) ),
+ htmlspecialchars( $label !== false ? $label :
$this->msg( 'wikibase-label-empty' )->text() ),
+ htmlspecialchars( $description !== false ?
$description : $this->msg( 'wikibase-description-empty' )->text() ),
+
$this->sectionEditLinkGenerator->getHtmlForEditSection( $editLabelLink,
$this->msg( 'wikibase-edit' ), 'span', $editable ),
+
$this->sectionEditLinkGenerator->getHtmlForEditSection( $editDescriptionLink,
$this->msg( 'wikibase-edit' ), 'span', $editable ),
+ $label !== false ? '' : 'wb-value-empty',
+ $description !== false ? '' : 'wb-value-empty',
+ $title->getLocalURL() . '?setlang=' . $language
+ );
+ }
+
+ $html = $html . wfTemplate( 'wb-terms-table', $tbody );
+
+ wfProfileOut( __METHOD__ );
+ return $html;
+ }
+
+ /**
+ * @param Entity $entity
+ *
+ * @return string
+ */
+ protected function getFormattedIdForEntity( Entity $entity ) {
+ if ( !$entity->getId() ) {
+ return ''; //XXX: should probably throw an exception?
+ }
+
+ return $entity->getId()->getPrefixedId();
+ }
+
+}
diff --git a/repo/includes/view/TextInjector.php
b/repo/includes/view/TextInjector.php
new file mode 100644
index 0000000..12e966f
--- /dev/null
+++ b/repo/includes/view/TextInjector.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Wikibase;
+use Message;
+
+/**
+ * Helper for injecting text by substituting placeholders.
+ * This class is designed to aid with the technique of putting placeholders
into
+ * cacheable HTML (in ParserOutput), and later replacing it with non-cacheable
HTML
+ * snippets (for use by OutputPage).
+ *
+ * @since 0.5
+ * @licence GNU GPL v2+
+ *
+ * @author Daniel Kinzler
+ */
+class TextInjector {
+
+ /**
+ * @var string
+ */
+ protected $uniqPrefix;
+
+ /**
+ * @var int
+ */
+ protected $markerIndex;
+
+ /**
+ * @var array string -> array
+ */
+ protected $markers;
+
+ /**
+ * @param array $markers markers generated by another instance of
TextInjector,
+ * for use by inject(); a map of string markers associated with
+ * parameter arrays.
+ */
+ public function __construct( $markers = array() ) {
+ $this->markers = $markers;
+
+ // idea stolen from Parser class in core
+ $this->uniqPrefix = "\x7fUNIQ" . wfRandom( 16 );
+ $this->markerIndex = 0;
+ }
+
+ /**
+ * Associates a new marker with the given parameters, and returns it.
+ * All parameters passed to this function will be associated with the
marker.
+ *
+ * @param $name
+ * @param ...
+ *
+ * @return string
+ */
+ public function newMarker( $name /* ... */ ) {
+ $marker = '$' . $this->uniqPrefix . '#' . ++$this->markerIndex
. '$';
+ $args = func_get_args();
+
+ $this->markers[$marker] = $args;
+ return $marker;
+ }
+
+ /**
+ * Returns a map of markers associated with lists of arguments.
+ *
+ * @return array[] marker -> array
+ */
+ public function getMarkers() {
+ return $this->markers;
+ }
+
+ /**
+ * Replaces the markers in $html by calling $callback for each marker
in $markers,
+ * passing the arguments associated with each marker to $callback.
+ *
+ * @param string $html
+ * @param callable $callback
+ *
+ * @return string
+ */
+ public function inject( $html, $callback ) {
+ $search = array();
+ $replace = array();
+
+ foreach ( $this->markers as $marker => $args ) {
+ $subst = call_user_func_array( $callback, $args );
+
+ if ( is_string( $subst ) ) {
+ $search[] = $marker;
+ $replace[] = $subst;
+ }
+ }
+
+ $html = str_replace( $search, $replace, $html );
+ return $html;
+ }
+}
diff --git a/repo/resources/themes/default/wikibase.toc.css
b/repo/resources/themes/default/wikibase.toc.css
index c62bbb4..9afd333 100644
--- a/repo/resources/themes/default/wikibase.toc.css
+++ b/repo/resources/themes/default/wikibase.toc.css
@@ -32,7 +32,7 @@
white-space: nowrap;
}
-.wb-entity #toc li.tocsection-1 {
+.wb-entity #toc li:first-child {
border-left: none;
padding-left: 0;
}
diff --git a/repo/tests/phpunit/includes/EntityViewPlaceholderExpanderTest.php
b/repo/tests/phpunit/includes/EntityViewPlaceholderExpanderTest.php
new file mode 100644
index 0000000..507b6ed
--- /dev/null
+++ b/repo/tests/phpunit/includes/EntityViewPlaceholderExpanderTest.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Wikibase\Test;
+use Language;
+use Title;
+use User;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\EntityViewPlaceholderExpander;
+use Wikibase\Item;
+
+
+/**
+ * @covers Wikibase\EntityViewPlaceholderExpander
+ *
+ * @since 0.4
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group EntityView
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class EntityViewPlaceholderExpanderTest extends \MediaWikiTestCase {
+
+ protected function newExpander() {
+ $title = new Title(
'EntityViewPlaceholderExpanderTest-DummyTitleForLocalUrls' );
+
+ $user = new User();
+ $user->setName( 'EntityViewPlaceholderExpanderTest-DummyUser' );
+
+ $language = Language::factory( 'en' );
+
+ $entity = Item::newEmpty();
+ $entity->setId( new ItemId( 'Q23' ) );
+
+ $entity->setLabel( 'en', 'Moskow' );
+ $entity->setLabel( 'de', 'Moskau' );
+
+ $entity->setDescription( 'de', 'Hauptstadt Russlands' );
+
+ $idParser = $this->getMockBuilder(
'Wikibase\DataModel\Entity\EntityIdParser' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $idParser->expects( $this->any() )
+ ->method( 'parse' )
+ ->will( $this->returnValue( $entity->getId() ) );
+
+ $entityLookup = $this->getMock( 'Wikibase\EntityLookup' );
+ $idParser->expects( $this->any() )
+ ->method( 'getEntity' )
+ ->will( $this->returnValue( $entity ) );
+
+ $userLanguages = $this->getMockBuilder(
'Wikibase\UserLanguageLookup' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $userLanguages->expects( $this->any() )
+ ->method( 'getUserLanguages' )
+ ->will( $this->returnValue( array( 'de', 'en', 'ru' ) )
);
+
+ return new EntityViewPlaceholderExpander(
+ $title,
+ $user,
+ $language,
+ $idParser,
+ $entityLookup,
+ $userLanguages
+ );
+ }
+
+ public function testGetHtmlForPlaceholder() {
+ $expander = $this->newExpander();
+
+ $html = $expander->getHtmlForPlaceholder( 'termbox-toc' );
+ $this->assertInternalType( 'string', $html );
+
+ $html = $expander->getHtmlForPlaceholder( 'termbox', 'Q23' );
+ $this->assertInternalType( 'string', $html );
+ }
+
+ public function testRenderTermBoxTocEntry() {
+ $expander = $this->newExpander();
+
+ // According to the mock objects, this should generate a term
box for
+ // 'de' and 'ru', since 'en' is already covered by the
interface language.
+ $html = $expander->renderTermBoxTocEntry( new ItemId( 'Q23' ) );
+
+ $this->assertRegExp( '/#wb-terms/', $html );
+ }
+
+ public function renderTermBox() {
+ $expander = $this->newExpander();
+
+ // According to the mock objects, this should generate a term
box for
+ // 'de' and 'ru', since 'en' is already covered by the
interface language.
+ $html = $expander->renderTermBox( new ItemId( 'Q23' ) );
+
+ $this->assertRegExp( '/Moskau/', $html );
+ $this->assertRegExp( '/Hauptstadt/', $html );
+
+ $this->assertNotRegExp( '/Moskow/', $html );
+ $this->assertNotRegExp( '/Capitol/', $html );
+ }
+
+}
\ No newline at end of file
diff --git a/repo/tests/phpunit/includes/EntityViewTest.php
b/repo/tests/phpunit/includes/EntityViewTest.php
index 4e977ea..a23e816 100644
--- a/repo/tests/phpunit/includes/EntityViewTest.php
+++ b/repo/tests/phpunit/includes/EntityViewTest.php
@@ -197,47 +197,6 @@
}
/**
- * @dataProvider getHtmlForEditSectionProvider
- */
- public function testGetHtmlForEditSection( $expected, $url, $tag,
$action, $enabled, $langCode ) {
- $entityView = $this->newEntityView( Item::ENTITY_TYPE );
-
- $context = $entityView->getContext();
- $context->setLanguage( $langCode );
- $entityView->setContext( $context );
-
- $editSectionHtml = $entityView->getHtmlForEditSection( $url,
$tag, $action, $enabled );
- $matcher = array(
- 'tag' => $tag,
- 'class' => 'wb-editsection'
- );
-
- $this->assertTag( $matcher, $editSectionHtml, "$action action"
);
- $this->assertRegExp( "/$expected/", $editSectionHtml, "$action
button label" );
- }
-
- public function getHtmlForEditSectionProvider() {
- return array(
- array(
- wfMessage( 'wikibase-edit' )->inLanguage( 'es'
)->text(),
- '',
- 'div',
- 'edit',
- true,
- 'es'
- ),
- array(
- wfMessage( 'wikibase-add' )->inLanguage( 'de'
)->text(),
- '',
- 'span',
- 'add',
- true,
- 'de'
- )
- );
- }
-
- /**
* @return array
*/
public function getHtmlForClaimsProvider() {
diff --git a/repo/tests/phpunit/includes/UserLanguagesTest.php
b/repo/tests/phpunit/includes/UserLanguagesTest.php
new file mode 100644
index 0000000..c5733ea
--- /dev/null
+++ b/repo/tests/phpunit/includes/UserLanguagesTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Wikibase\Test;
+use User;
+use Wikibase\UserLanguageLookup;
+
+/**
+ * @covers Wikibase\UserLanguages
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group EntityView
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class UserLanguagesTest extends \PHPUnit_Framework_TestCase {
+
+ public function testGetUserLanguages() {
+
+ $user = new User();
+ $user->setName( 'UserLanguagesTest-TestUser' );
+ $user->setOption( 'language', 'de' );
+
+ $userLanguages = new UserLanguageLookup();
+
+ //TODO: we really want to test grabbing languages from the
Babel extension,
+ // but how can we test that?
+
+ $this->assertContains( 'de', $userLanguages->getUserLanguages(
$user ) );
+ $this->assertNotContains( 'de',
$userLanguages->getUserLanguages( $user, array( 'de' ) ) );
+ }
+
+}
diff --git a/repo/tests/phpunit/includes/view/SectionEditLinkGeneratorTest.php
b/repo/tests/phpunit/includes/view/SectionEditLinkGeneratorTest.php
new file mode 100644
index 0000000..94b72e9
--- /dev/null
+++ b/repo/tests/phpunit/includes/view/SectionEditLinkGeneratorTest.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Wikibase\Test;
+
+use Wikibase\SectionEditLinkGenerator;
+
+/**
+ * @covers Wikibase\SectionEditLinkGenerator
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group EntityView
+ *
+ * @licence GNU GPL v2+
+ * @author Katie Filbert
+ * @author Daniel Kinzler
+ */
+class SectionEditLinkGeneratorTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * @dataProvider getHtmlForEditSectionProvider
+ */
+ public function testGetHtmlForEditSection( $expected, $url, $tag,
$action, $enabled, $langCode ) {
+ $generator = new SectionEditLinkGenerator();
+
+ $key = $action === 'add' ? 'wikibase-add' : 'wikibase-edit';
+ $msg = wfMessage( $key )->inLanguage( $langCode );
+
+ $editSectionHtml = $generator->getHtmlForEditSection( $url,
$msg, $tag, $enabled );
+ $matcher = array(
+ 'tag' => $tag,
+ 'class' => 'wb-editsection'
+ );
+
+ $this->assertTag( $matcher, $editSectionHtml, "$action action"
);
+ $this->assertRegExp( $expected, $editSectionHtml, "$action
button label" );
+ }
+
+ public function getHtmlForEditSectionProvider() {
+ return array(
+ array(
+ '/' . wfMessage( 'wikibase-edit' )->inLanguage(
'es' )->text() . '/',
+ '',
+ 'div',
+ 'edit',
+ true,
+ 'es'
+ ),
+ array(
+ '/' . wfMessage( 'wikibase-add' )->inLanguage(
'de' )->text() . '/',
+ '',
+ 'span',
+ 'add',
+ true,
+ 'de'
+ )
+ );
+ }
+
+
+}
diff --git a/repo/tests/phpunit/includes/view/TermBoxViewTest.php
b/repo/tests/phpunit/includes/view/TermBoxViewTest.php
new file mode 100644
index 0000000..d7dcadf
--- /dev/null
+++ b/repo/tests/phpunit/includes/view/TermBoxViewTest.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Wikibase\Test;
+use Language;
+use Title;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\Item;
+use Wikibase\TermBoxView;
+
+/**
+ * @covers Wikibase\TermBoxView
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group EntityView
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class TermBoxViewTest extends \PHPUnit_Framework_TestCase {
+
+ public function testRenderTermBox() {
+ $language = Language::factory( 'qqx' ); // so we can look for
message keys in the output
+ $view = new TermBoxView( $language );
+
+ $title = Title::newFromText( 'TermBoxViewTest-DummyTitle' );
+
+ $entity = Item::newEmpty();
+ $entity->setId( new ItemId( 'Q23' ) );
+
+ $entity->setLabel( 'en', 'Moskow' );
+ $entity->setLabel( 'de', 'Moskau' );
+
+ $entity->setDescription( 'de', 'Hauptstadt Russlands' );
+
+ $languages = array( 'de', 'ru' );
+
+ $html = $view->renderTermBox( $title, $entity, $languages );
+
+ $this->assertNotRegExp( '/Moskow/', $html, 'unexpected English
label, should not be there' );
+
+ $this->assertRegExp( '/Moskau/', $html, 'expected German label'
);
+ $this->assertRegExp( '/Hauptstadt/', $html, 'expected German
description' );
+
+ $this->assertRegExp( '/wikibase-label-empty/', $html, 'expected
label-empty message for "ru"' );
+ $this->assertRegExp( '!Q23/de!', $html, 'expected edit link for
Q23/de' );
+ $this->assertRegExp( '!<h2
id="wb-terms".*?>\(wikibase-terms\)</h2>!', $html, 'expected h2 header' );
+ }
+
+}
diff --git a/repo/tests/phpunit/includes/view/TextInjectorTest.php
b/repo/tests/phpunit/includes/view/TextInjectorTest.php
new file mode 100644
index 0000000..6ea244f
--- /dev/null
+++ b/repo/tests/phpunit/includes/view/TextInjectorTest.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Wikibase\Test;
+use Wikibase\TextInjector;
+
+/**
+ * @covers Wikibase\TextInjector
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group EntityView
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class TextInjectorTest extends \PHPUnit_Framework_TestCase {
+
+ public function testConstructor() {
+ $injector = new TextInjector();
+ $this->assertEmpty( $injector->getMarkers() );
+
+ $injector = new TextInjector( array( 'test' => array( 'foo',
'bar' ) ) );
+ $this->assertArrayHasKey( 'test', $injector->getMarkers() );
+ }
+
+ public function testNewMarker() {
+ $injector = new TextInjector();
+
+ $foo = $injector->newMarker( 'foo' );
+ $bar = $injector->newMarker( 'bar', 1, 2, 3 );
+
+ $markers = $injector->getMarkers();
+
+ $this->assertArrayHasKey( $foo, $markers );
+ $this->assertEquals( array( 'foo' ), $markers[$foo] );
+
+ $this->assertArrayHasKey( $bar, $markers );
+ $this->assertEquals( array( 'bar', 1, 2, 3 ), $markers[$bar] );
+ }
+
+ public function testInject() {
+ $injector = new TextInjector();
+
+ $text = 'Good ' . $injector->newMarker( 'morning' )
+ . ' to ' . $injector->newMarker( 'you', 'all' ) . '!';
+
+ $expected = 'Good morning to you all!';
+
+ $actual = $injector->inject( $text, function () {
+ $args = func_get_args();
+ return implode( ' ', $args );
+ } );
+
+ $this->assertEquals( $expected, $actual );
+ }
+
+}
--
To view, visit https://gerrit.wikimedia.org/r/101064
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I75cbbf01c644232f906a1d4f8e990b3fdbf781ec
Gerrit-PatchSet: 8
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Aude <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Tobias Gritschacher <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits