Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SubpageRecommendationProvider
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 5
132
0.00% covered (danger)
0.00%
0 / 1
 createRecommendation
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
0
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 get
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
42
 onMediaWikiServices
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 onContentHandlerDefaultModelFor
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace GrowthExperiments\NewcomerTasks;
4
5use GrowthExperiments\NewcomerTasks\TaskType\TaskType;
6use InvalidArgumentException;
7use MediaWiki\Content\JsonContent;
8use MediaWiki\Json\FormatJson;
9use MediaWiki\Linker\LinkTarget;
10use MediaWiki\MediaWikiServices;
11use MediaWiki\Page\WikiPageFactory;
12use MediaWiki\Title\Title;
13use MediaWiki\Title\TitleValue;
14use StatusValue;
15use 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 */
27abstract 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}