Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
20.41% covered (danger)
20.41%
10 / 49
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
JsonSchemaContent
20.41% covered (danger)
20.41%
10 / 49
37.50% covered (danger)
37.50%
3 / 8
145.08
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resolve
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 expand
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 getJsonData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validate
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 isValid
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 objectRow
16.67% covered (danger)
16.67%
2 / 12
0.00% covered (danger)
0.00%
0 / 1
8.21
 getCodeSamples
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * JSON Schema Content Model
4 *
5 * @file
6 * @ingroup Extensions
7 * @ingroup EventLogging
8 *
9 * @author Ori Livneh <ori@wikimedia.org>
10 */
11
12namespace MediaWiki\Extension\EventLogging;
13
14use FormatJson;
15use JsonContent;
16use JsonSchemaException;
17use MediaWiki\Html\Html;
18use MediaWiki\MediaWikiServices;
19use Xml;
20
21/**
22 * Represents the content of a JSON Schema article.
23 */
24class JsonSchemaContent extends JsonContent {
25
26    private const DEFAULT_RECURSION_LIMIT = 3;
27
28    public function __construct( $text, $modelId = 'JsonSchema' ) {
29        parent::__construct( $text, $modelId );
30    }
31
32    /**
33     * Resolve a JSON reference to a schema.
34     * @param string $ref Schema reference with format 'Title/Revision'
35     * @return array|bool
36     */
37    public static function resolve( $ref ) {
38        [ $title, $revId ] = explode( '/', $ref );
39        $rs = new RemoteSchema( $title, (int)$revId );
40        return $rs->get();
41    }
42
43    /**
44     * Recursively resolve references in a schema.
45     * @param array $schema Schema object to expand
46     * @param int $recursionLimit Maximum recursion limit
47     * @return array Expanded schema object
48     */
49    public static function expand( $schema,
50            $recursionLimit = self::DEFAULT_RECURSION_LIMIT ) {
51        return array_map( static function ( $value ) use( $recursionLimit ) {
52            if ( is_array( $value ) && $recursionLimit > 0 ) {
53                if ( isset( $value['$ref'] ) ) {
54                    $value = JsonSchemaContent::resolve( $value['$ref'] );
55                }
56                return JsonSchemaContent::expand( $value, $recursionLimit - 1 );
57            }
58            return $value;
59        }, $schema );
60    }
61
62    /**
63     * Decodes the JSON schema into a PHP associative array.
64     * @return array Schema array
65     */
66    public function getJsonData() {
67        return FormatJson::decode( $this->getText(), true );
68    }
69
70    /**
71     * @throws JsonSchemaException If content is invalid
72     * @return bool True if valid
73     */
74    public function validate() {
75        $schema = $this->getJsonData();
76        if ( !is_array( $schema ) ) {
77            throw new JsonSchemaException( 'eventlogging-invalid-json' );
78        }
79        return EventLogging::schemaValidate( $schema );
80    }
81
82    /**
83     * @return bool Whether content is valid JSON Schema.
84     */
85    public function isValid() {
86        try {
87            return parent::isValid() && $this->validate();
88        } catch ( JsonSchemaException $e ) {
89            return false;
90        }
91    }
92
93    /**
94     * Constructs HTML representation of a single key-value pair.
95     * Override this to support $ref
96     * @param string $key
97     * @param mixed $val
98     * @return string HTML
99     */
100    public function objectRow( $key, $val ) {
101        if ( $key === '$ref' ) {
102            $valParts = explode( '/', $val, 2 );
103            if ( !isset( $valParts[1] ) ) {
104                // Don't store or inject service objects in Content objects
105                // as that breaks serialization (T286610).
106                $services = MediaWikiServices::getInstance();
107                $revId = (int)$valParts[1];
108                $revRecord = $services->getRevisionLookup()->getRevisionById( $revId );
109                $title = $revRecord->getPageAsLinkTarget();
110                $link = $services->getLinkRenderer()->makeLink( $title, $val, [], [ 'oldid' => $revId ] );
111                $th = Xml::elementClean( 'th', [], $key );
112                $td = Xml::tags( 'td', [ 'class' => 'value' ], $link );
113                return Html::rawElement( 'tr', [], $th . $td );
114            }
115        }
116
117        return parent::objectRow( $key, $val );
118    }
119
120    /**
121     * Generate generic PHP and JavaScript code strings showing how to
122     * use a schema.
123     * @param string $dbKey DB key of schema article
124     * @param int $revId Revision ID of schema article
125     * @return array[] Nested array with each sub-array having a language, header
126     *  (message key), and code
127     */
128    public function getCodeSamples( $dbKey, $revId ) {
129        return [
130            [
131                'language' => 'php',
132                'header' => 'eventlogging-code-sample-logging-on-server-side',
133                'code' => "EventLogging::logEvent( '$dbKey', $revId, \$event );",
134            ], [
135                'language' => 'json',
136                'header' => 'eventlogging-code-sample-module-setup-json',
137                'code' => FormatJson::encode( [
138                    'attributes' => [ 'EventLogging' => [
139                        'Schemas' => [ $dbKey => $revId, ] ]
140                    ] ], "\t" ),
141            ], [
142                'language' => 'javascript',
143                'header' => 'eventlogging-code-sample-logging-on-client-side',
144                'code' => "mw.track( 'event.{$dbKey}', { /* ... */ } );",
145            ],
146        ];
147    }
148}