Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.33% covered (warning)
85.33%
64 / 75
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
SchemaOrg
85.33% covered (warning)
85.33%
64 / 75
37.50% covered (danger)
37.50%
3 / 8
19.02
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%
25 / 25
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 OutputPage;
30use Title;
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( $template, json_encode( $meta ) )
117        );
118    }
119
120    /**
121     * @inheritDoc
122     */
123    public function getAllowedParameterNames(): array {
124        return $this->tags;
125    }
126
127    /**
128     * Generate proper schema.org type in order to pass validation
129     *
130     * @return string
131     */
132    private function getTypeMetadata(): string {
133        return $this->metadata['type'] ?? 'Article';
134    }
135
136    /**
137     * Generate jsonld metadata from the supplied file name, configured default image or wiki logo
138     *
139     * @return array
140     */
141    private function getImageMetadata(): array {
142        $data = [
143            '@type' => 'ImageObject',
144        ];
145
146        $this->setFallbackImageIfEnabled();
147
148        if ( isset( $this->metadata['image'] ) ) {
149            $image = $this->metadata['image'];
150
151            try {
152                $file = $this->getFileObject( $image );
153
154                return array_merge( $data, $this->getFileInfo( $file ) );
155            } catch ( InvalidArgumentException $e ) {
156                // Fallthrough
157            }
158        }
159
160        // Logo as Fallback
161        return $this->getLogoMetadata();
162    }
163
164    /**
165     * Add the sitename as the author
166     *
167     * @return array
168     */
169    private function getAuthorMetadata(): array {
170        $sitename = $this->getConfigValue( 'Sitename' ) ?? '';
171        $server = $this->getConfigValue( 'Server' ) ?? '';
172
173        $logo = $this->getLogoMetadata();
174
175        if ( !empty( $logo ) ) {
176            $logo['caption'] = $sitename;
177        }
178
179        return [
180            '@type' => 'Organization',
181            'name' => $sitename,
182            'url' => $server,
183            'logo' => $logo,
184        ];
185    }
186
187    /**
188     * Tries to get the main logo form config as an expanded url
189     *
190     * @return array
191     */
192    private function getLogoMetadata(): array {
193        $data = [
194            '@type' => 'ImageObject',
195        ];
196
197        if ( $this->getConfigValue( 'WikiSeoDisableLogoFallbackImage' ) === true ) {
198            return $data;
199        }
200
201        try {
202            $logo = $this->getWikiLogo();
203            if ( $logo !== false ) {
204                $data['url'] = $logo;
205            } else {
206                $data = [];
207            }
208        } catch ( Exception $e ) {
209            // Uh oh either there was a ConfigException or there was an error expanding the URL.
210            // We'll bail out.
211            $data = [];
212        }
213
214        return $data;
215    }
216
217    /**
218     * Add search action metadata
219     * https://gitlab.com/hydrawiki/extensions/seo/blob/master/SEOHooks.php
220     *
221     * @return array
222     */
223    private function getSearchActionMetadata(): array {
224        $searchPage = Title::newFromText( 'Special:Search' );
225
226        if ( $searchPage !== null ) {
227            $search =
228                $searchPage->getFullURL( [ 'search' => 'search_term' ], false,
229                    sprintf( '%s://', $this->outputPage->getRequest()->getProtocol() ) );
230            $search = str_replace( 'search_term', '{search_term}', $search );
231
232            return [
233                '@type' => 'SearchAction',
234                'target' => $search,
235                'query-input' => 'required name=search_term',
236            ];
237        }
238
239        return [];
240    }
241}