Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 41 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
SubpageRecommendationProvider | |
0.00% |
0 / 41 |
|
0.00% |
0 / 5 |
132 | |
0.00% |
0 / 1 |
createRecommendation | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
0 | |||
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
get | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
42 | |||
onMediaWikiServices | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
onContentHandlerDefaultModelFor | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\NewcomerTasks; |
4 | |
5 | use GrowthExperiments\NewcomerTasks\TaskType\TaskType; |
6 | use InvalidArgumentException; |
7 | use MediaWiki\Content\JsonContent; |
8 | use MediaWiki\Json\FormatJson; |
9 | use MediaWiki\Linker\LinkTarget; |
10 | use MediaWiki\MediaWikiServices; |
11 | use MediaWiki\Page\WikiPageFactory; |
12 | use MediaWiki\Title\Title; |
13 | use MediaWiki\Title\TitleValue; |
14 | use StatusValue; |
15 | use Wikimedia\Assert\Assert; |
16 | |
17 | /** |
18 | * Base class for recommendation providers for testing purposes. Looks for a `/<type>.json` |
19 | * subpage of the target page, and returns the contents of that page as link data. `<type>` |
20 | * is a keyword that's different for each recommendation type. |
21 | * |
22 | * Enable the subclass(es) you want by adding the following to LocalSettings.php or a similar place: |
23 | * $class = <subpage recommendation provider subclass>; |
24 | * $wgHooks['MediaWikiServices'][] = "$class::onMediaWikiServices"; |
25 | * $wgHooks['ContentHandlerDefaultModelFor'][] = "$class::onContentHandlerDefaultModelFor"; |
26 | */ |
27 | abstract class SubpageRecommendationProvider implements RecommendationProvider { |
28 | |
29 | /** @var string The name to use for subpages (without .json). Subclasses must provide this. */ |
30 | protected static $subpageName = null; |
31 | |
32 | /** |
33 | * @var string The MediaWiki service to replace, which returns the provider object. |
34 | * Subclasses must provide this. |
35 | */ |
36 | protected static $serviceName = null; |
37 | |
38 | /** @var string|array Name of the Recommendation subclass(es). Subclasses of this class must provide this. */ |
39 | protected static $recommendationTaskTypeClass = null; |
40 | |
41 | /** @var WikiPageFactory */ |
42 | private $wikiPageFactory; |
43 | |
44 | /** @var RecommendationProvider */ |
45 | private $fallbackRecommendationProvider; |
46 | |
47 | /** |
48 | * Create the recommendation object from the JSON representation. |
49 | * @param Title $title The page the recommendation is for. |
50 | * @param TaskType $taskType |
51 | * @param array $data Recommendation data (ie. the content of the JSON subpage). |
52 | * @param array $suggestionFilters |
53 | * @return Recommendation|StatusValue |
54 | */ |
55 | abstract public function createRecommendation( |
56 | Title $title, |
57 | TaskType $taskType, |
58 | array $data, |
59 | array $suggestionFilters = [] |
60 | ); |
61 | |
62 | /** |
63 | * @param WikiPageFactory $wikiPageFactory |
64 | * @param RecommendationProvider $fallbackRecommendationProvider |
65 | */ |
66 | public function __construct( |
67 | WikiPageFactory $wikiPageFactory, |
68 | RecommendationProvider $fallbackRecommendationProvider |
69 | ) { |
70 | Assert::precondition( static::$subpageName !== null, |
71 | 'subclasses must override $subpageName' ); |
72 | $this->wikiPageFactory = $wikiPageFactory; |
73 | $this->fallbackRecommendationProvider = $fallbackRecommendationProvider; |
74 | } |
75 | |
76 | /** @inheritDoc */ |
77 | public function get( LinkTarget $title, TaskType $taskType ) { |
78 | Assert::parameterType( static::$recommendationTaskTypeClass, $taskType, '$taskType' ); |
79 | |
80 | $subpageName = static::$subpageName; |
81 | $subpageTitleText = $title->getDBkey() . "/$subpageName.json"; |
82 | $subpageTitle = new TitleValue( $title->getNamespace(), $subpageTitleText ); |
83 | try { |
84 | $subpage = $this->wikiPageFactory->newFromLinkTarget( $subpageTitle ); |
85 | } catch ( InvalidArgumentException $e ) { |
86 | // happens for nonsensical namespaces, like Media: |
87 | return StatusValue::newFatal( 'rawmessage', $e->getMessage() ); |
88 | } |
89 | |
90 | if ( !$subpage->exists() ) { |
91 | if ( $this->fallbackRecommendationProvider ) { |
92 | return $this->fallbackRecommendationProvider->get( $title, $taskType ); |
93 | } else { |
94 | // This is a development-only provider, no point in translating its messages. |
95 | return StatusValue::newFatal( 'rawmessage', "No /$subpageName.json subpage found" ); |
96 | } |
97 | } |
98 | |
99 | $content = $subpage->getContent(); |
100 | if ( !$content instanceof JsonContent ) { |
101 | return StatusValue::newFatal( 'rawmessage', |
102 | "/$subpageName.json subpage is not a JSON page." ); |
103 | } |
104 | $dataStatus = FormatJson::parse( $content->getText(), FormatJson::FORCE_ASSOC ); |
105 | if ( !$dataStatus->isOK() ) { |
106 | return $dataStatus; |
107 | } |
108 | $data = $dataStatus->getValue(); |
109 | |
110 | // Turn $title into a real Title |
111 | $title = $this->wikiPageFactory->newFromLinkTarget( $title )->getTitle(); |
112 | |
113 | return $this->createRecommendation( $title, $taskType, $data, $taskType->getSuggestionFilters() ); |
114 | } |
115 | |
116 | /** |
117 | * MediaWikiServices hook handler, for development setups only. |
118 | * @param MediaWikiServices $services |
119 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/MediaWikiServices |
120 | */ |
121 | public static function onMediaWikiServices( MediaWikiServices $services ) { |
122 | Assert::precondition( static::$serviceName !== null, |
123 | 'subclasses must override $serviceName' ); |
124 | $services->addServiceManipulator( static::$serviceName, |
125 | static function ( |
126 | RecommendationProvider $recommendationProvider, |
127 | MediaWikiServices $services |
128 | ) { |
129 | return new static( $services->getWikiPageFactory(), $recommendationProvider ); |
130 | } ); |
131 | } |
132 | |
133 | /** |
134 | * ContentHandlerDefaultModelFor hook handler, for development setups only. |
135 | * @param Title $title |
136 | * @param string &$model |
137 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ContentHandlerDefaultModelFor |
138 | */ |
139 | public static function onContentHandlerDefaultModelFor( Title $title, &$model ) { |
140 | // This is for development, so we want to ignore $wgNamespacesWithSubpages. |
141 | $titleText = $title->getText(); |
142 | $titleParts = explode( '/', $titleText ); |
143 | $subpage = end( $titleParts ); |
144 | if ( $subpage === static::$subpageName . '.json' && $subpage !== $titleText ) { |
145 | $model = CONTENT_MODEL_JSON; |
146 | } |
147 | } |
148 | |
149 | } |