Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.90% covered (warning)
85.90%
67 / 78
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
SchemaOrg
85.90% covered (warning)
85.90%
67 / 78
37.50% covered (danger)
37.50%
3 / 8
18.91
0.00% covered (danger)
0.00%
0 / 1
 init
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addMetadata
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
4
 getAllowedParameterNames
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTypeMetadata
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getImageMetadata
60.00% covered (warning)
60.00%
6 / 10
0.00% covered (danger)
0.00%
0 / 1
3.58
 getAuthorMetadata
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
2.00
 getLogoMetadata
66.67% covered (warning)
66.67%
8 / 12
0.00% covered (danger)
0.00%
0 / 1
4.59
 getSearchActionMetadata
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
2.00
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 *
17 * @file
18 */
19
20declare( strict_types=1 );
21
22namespace MediaWiki\Extension\WikiSEO\Generator\Plugins;
23
24use Exception;
25use InvalidArgumentException;
26use MediaWiki\Extension\WikiSEO\Generator\AbstractBaseGenerator;
27use MediaWiki\Extension\WikiSEO\Generator\GeneratorInterface;
28use MediaWiki\Extension\WikiSEO\WikiSEO;
29use MediaWiki\Title\Title;
30use OutputPage;
31
32class SchemaOrg extends AbstractBaseGenerator implements GeneratorInterface {
33    /**
34     * Valid Tags for this generator
35     *
36     * @var array
37     */
38    protected $tags = [
39        'type',
40        'description',
41        'keywords',
42        'modified_time',
43        'published_time',
44        'section'
45    ];
46
47    /**
48     * Tag name conversions for this generator
49     *
50     * @var array
51     */
52    protected $conversions = [
53        'type' => '@type',
54
55        'section' => 'articleSection',
56
57        'published_time' => 'datePublished',
58        'modified_time'  => 'dateModified'
59    ];
60
61    /**
62     * Initialize the generator with all metadata and the page to output the metadata onto
63     *
64     * @param array $metadata All metadata
65     * @param OutputPage $out The page to add the metadata to
66     *
67     * @return void
68     */
69    public function init( array $metadata, OutputPage $out ): void {
70        $this->metadata = $metadata;
71        $this->outputPage = $out;
72
73        $this->setModifiedPublishedTime();
74    }
75
76    /**
77     * Add the metadata to the OutputPage
78     *
79     * @return void
80     */
81    public function addMetadata(): void {
82        $template = '<script type="application/ld+json">%s</script>';
83
84        $meta = [
85            '@context' => 'http://schema.org',
86            '@type' => $this->getTypeMetadata(),
87            'name' => $this->outputPage->getHTMLTitle(),
88            'headline' => $this->outputPage->getHTMLTitle(),
89            'mainEntityOfPage' => strip_tags( $this->outputPage->getPageTitle() ),
90        ];
91
92        if ( $this->outputPage->getTitle() !== null ) {
93            $url = $this->outputPage->getTitle()->getFullURL();
94
95            $url = WikiSEO::protocolizeUrl( $url, $this->outputPage->getRequest() );
96
97            $meta['identifier'] = $url;
98            $meta['url'] = $url;
99        }
100
101        foreach ( $this->tags as $tag ) {
102            if ( array_key_exists( $tag, $this->metadata ) ) {
103                $convertedTag = $this->conversions[$tag] ?? $tag;
104
105                $meta[$convertedTag] = $this->metadata[$tag];
106            }
107        }
108
109        $meta['image'] = $this->getImageMetadata();
110        $meta['author'] = $this->getAuthorMetadata();
111        $meta['publisher'] = $this->getAuthorMetadata();
112        $meta['potentialAction'] = $this->getSearchActionMetadata();
113
114        $this->outputPage->addHeadItem(
115            'jsonld-metadata',
116            sprintf(
117                $template,
118                json_encode( $meta ),
119            )
120        );
121    }
122
123    /**
124     * @inheritDoc
125     */
126    public function getAllowedParameterNames(): array {
127        return $this->tags;
128    }
129
130    /**
131     * Generate proper schema.org type in order to pass validation
132     *
133     * @return string
134     */
135    private function getTypeMetadata(): string {
136        return $this->metadata['type'] ?? 'Article';
137    }
138
139    /**
140     * Generate jsonld metadata from the supplied file name, configured default image or wiki logo
141     *
142     * @return array
143     */
144    private function getImageMetadata(): array {
145        $data = [
146            '@type' => 'ImageObject',
147        ];
148
149        $this->setFallbackImageIfEnabled();
150
151        if ( isset( $this->metadata['image'] ) ) {
152            $image = $this->metadata['image'];
153
154            try {
155                $file = $this->getFileObject( $image );
156
157                return array_merge( $data, $this->getFileInfo( $file ) );
158            } catch ( InvalidArgumentException ) {
159                // Fallthrough
160            }
161        }
162
163        // Logo as Fallback
164        return $this->getLogoMetadata();
165    }
166
167    /**
168     * Add the sitename as the author
169     *
170     * @return array
171     */
172    private function getAuthorMetadata(): array {
173        $sitename = $this->getConfigValue( 'Sitename' ) ?? '';
174        $server = $this->getConfigValue( 'Server' ) ?? '';
175
176        $logo = $this->getLogoMetadata();
177
178        if ( !empty( $logo ) ) {
179            $logo['caption'] = $sitename;
180        }
181
182        return [
183            '@type' => 'Organization',
184            'name' => $sitename,
185            'url' => $server,
186            'logo' => $logo,
187        ];
188    }
189
190    /**
191     * Tries to get the main logo form config as an expanded url
192     *
193     * @return array
194     */
195    private function getLogoMetadata(): array {
196        $data = [
197            '@type' => 'ImageObject',
198        ];
199
200        if ( $this->getConfigValue( 'WikiSeoDisableLogoFallbackImage' ) === true ) {
201            return $data;
202        }
203
204        try {
205            $logo = $this->getWikiLogo();
206            if ( $logo !== false ) {
207                $data['url'] = $logo;
208            } else {
209                $data = [];
210            }
211        } catch ( Exception ) {
212            // Uh oh either there was a ConfigException or there was an error expanding the URL.
213            // We'll bail out.
214            $data = [];
215        }
216
217        return $data;
218    }
219
220    /**
221     * Add search action metadata
222     * https://gitlab.com/hydrawiki/extensions/seo/blob/master/SEOHooks.php
223     *
224     * @return array
225     */
226    private function getSearchActionMetadata(): array {
227        $searchPage = Title::newFromText( 'Special:Search' );
228
229        if ( $searchPage !== null ) {
230            $search =
231                $searchPage->getFullURL( [ 'search' => 'search_term' ], false,
232                    sprintf( '%s://', $this->outputPage->getRequest()->getProtocol() ) );
233            $search = str_replace( 'search_term', '{search_term}', $search );
234
235            return [
236                '@type' => 'SearchAction',
237                'target' => $search,
238                'query-input' => 'required name=search_term',
239            ];
240        }
241
242        return [];
243    }
244}