Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
53.49% covered (warning)
53.49%
46 / 86
25.00% covered (danger)
25.00%
2 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractBaseGenerator
53.49% covered (warning)
53.49%
46 / 86
25.00% covered (danger)
25.00%
2 / 8
142.58
0.00% covered (danger)
0.00%
0 / 1
 getConfigValue
33.33% covered (danger)
33.33%
4 / 12
0.00% covered (danger)
0.00%
0 / 1
5.67
 getFileObject
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 getFileInfo
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 preprocessFileMetadata
57.14% covered (warning)
57.14%
8 / 14
0.00% covered (danger)
0.00%
0 / 1
8.83
 getRevisionTimestamp
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
3.00
 setFallbackImageIfEnabled
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 getWikiLogo
64.29% covered (warning)
64.29%
9 / 14
0.00% covered (danger)
0.00%
0 / 1
9.23
 setModifiedPublishedTime
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
6
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;
23
24use Config;
25use ConfigException;
26use Exception;
27use ExtensionRegistry;
28use File;
29use InvalidArgumentException;
30use MediaWiki\Extension\WikiSEO\WikiSEO;
31use MediaWiki\MediaWikiServices;
32use OutputPage;
33use PageImages\PageImages;
34
35abstract class AbstractBaseGenerator {
36    /**
37     * Usually the metadata from the constructor
38     *
39     * @var array
40     */
41    protected $metadata;
42
43    /**
44     * Valid Tags for this generator
45     *
46     * @var array
47     */
48    protected $tags = [];
49
50    /**
51     * @var OutputPage
52     */
53    protected $outputPage;
54
55    /**
56     * True if the wiki logo is used as the current fallback image
57     *
58     * @var bool
59     */
60    protected $fallbackImageActive = false;
61
62    /**
63     * The WikiSEO Config object
64     *
65     * @var Config
66     */
67    private static $config;
68
69    /**
70     * Loads a config value for a given key from the main config
71     * Returns null on if an ConfigException was thrown
72     *
73     * @param string $key The config key
74     *
75     * @return mixed|null
76     */
77    protected function getConfigValue( string $key ) {
78        if ( self::$config === null ) {
79            self::$config = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'WikiSEO' );
80        }
81
82        try {
83            $value = self::$config->get( $key );
84        } catch ( ConfigException $e ) {
85            wfLogWarning(
86                sprintf(
87                    'Could not get config for "$wg%s". %s', $key,
88                    $e->getMessage()
89                )
90            );
91            $value = null;
92        }
93
94        return $value;
95    }
96
97    /**
98     * Returns a file object by name, throws InvalidArgument if not found.
99     *
100     * @param string $name Filename
101     *
102     * @return File
103     *
104     * @throws InvalidArgumentException
105     */
106    protected function getFileObject( string $name ): File {
107        // This should remove the namespace if present
108        $nameSplit = explode( ':', $name );
109        $name = array_pop( $nameSplit ) ?? '';
110
111        $title = MediaWikiServices::getInstance()->getTitleFactory()->makeTitle(
112            NS_FILE,
113            $name
114        );
115
116        $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $title );
117
118        if ( $file === false ) {
119            throw new InvalidArgumentException( sprintf( 'File %s not found.', $name ) );
120        }
121
122        return $file;
123    }
124
125    /**
126     * Generate file metadata from a local file object
127     *
128     * @param File $file
129     *
130     * @return array
131     */
132    protected function getFileInfo( File $file ): array {
133        $cacheHash =
134            '?version=' . md5( $file->getTimestamp() . $file->getWidth() . $file->getHeight() );
135        $width = $file->getWidth();
136        $height = $file->getHeight();
137        $image = WikiSEO::protocolizeUrl( $file->getFullUrl(), $this->outputPage->getRequest() );
138
139        return [
140            'url' => $image . $cacheHash,
141            'width' => $width,
142            'height' => $height,
143        ];
144    }
145
146    /**
147     * If the metadata key 'image' is set, try to get file info of the local file
148     */
149    protected function preprocessFileMetadata(): void {
150        if ( isset( $this->metadata['image'] ) ) {
151            try {
152                $file = $this->getFileObject( $this->metadata['image'] );
153                $info = $this->getFileInfo( $file );
154
155                $this->metadata['image'] = $info['url'];
156                $this->metadata['image_width'] = $info['width'];
157                $this->metadata['image_height'] = $info['height'];
158            } catch ( InvalidArgumentException $e ) {
159                // File does not exist.
160                // Maybe the user has set an URL, should we do something?
161            }
162        } else {
163            if ( $this->getConfigValue( 'WikiSeoDisableLogoFallbackImage' ) === true ) {
164                return;
165            }
166
167            try {
168                $logo = $this->getWikiLogo();
169
170                if ( $logo !== false ) {
171                    $this->metadata['image'] = $logo;
172                    $this->fallbackImageActive = true;
173                }
174            } catch ( Exception $e ) {
175                // We do nothing
176            }
177        }
178    }
179
180    /**
181     * Tries to load the current revision timestamp for the page or current timestamp if nothing
182     * could be found.
183     *
184     * @return bool|string
185     */
186    protected function getRevisionTimestamp() {
187        $timestamp = $this->outputPage->getRevisionTimestamp();
188
189        // No cached timestamp, load it from the database
190        if ( $timestamp === null ) {
191            $timestamp =
192                MediaWikiServices::getInstance()
193                    ->getRevisionLookup()
194                    ->getKnownCurrentRevision(
195                        $this->outputPage->getTitle(),
196                        $this->outputPage->getRevisionId()
197                    );
198
199            if ( $timestamp === false ) {
200                $timestamp = wfTimestampNow();
201            } else {
202                $timestamp = $timestamp->getTimestamp() ?? wfTimestampNow();
203            }
204        }
205
206        return wfTimestamp( TS_ISO_8601, $timestamp );
207    }
208
209    /**
210     * Sets a fallback image if no '|image=' parameter was given AND the page does not have a page image
211     *
212     * @return void
213     */
214    protected function setFallbackImageIfEnabled(): void {
215        $continue = true;
216
217        if ( ExtensionRegistry::getInstance()->isLoaded( 'PageImages' ) ) {
218            $continue = PageImages::getPageImage( $this->outputPage->getTitle() ) === false;
219        }
220
221        if ( !isset( $this->metadata['image'] ) && $continue ) {
222            $defaultImage = $this->getConfigValue( 'WikiSeoDefaultImage' );
223
224            if ( $defaultImage !== null ) {
225                $this->metadata['image'] = $defaultImage;
226            }
227        }
228    }
229
230    /**
231     * Tries to return the expanded url to the wiki's logo based on $wgLogos or $wgLogo
232     * If nothing was found, or no url could be generated, returns false
233     *
234     * @return string|false
235     * @throws Exception
236     */
237    protected function getWikiLogo() {
238        $logos = $this->getConfigValue( 'Logos' );
239        if ( $logos === false || !is_array( $logos ) ) {
240            $logo = $this->getConfigValue( 'Logo' );
241
242            if ( is_string( $logo ) ) {
243                return MediaWikiServices::getInstance()->getUrlUtils()->expand( $logo ) ?? false;
244            }
245
246            return false;
247        }
248
249        foreach ( $logos as $path ) {
250            if ( !is_string( $path ) ) {
251                continue;
252            }
253
254            $parts = explode( '.', $path );
255            $ext = array_pop( $parts ) ?? '';
256            if ( in_array( strtolower( $ext ), [ 'jpg', 'jpeg', 'png', 'gif', 'webp' ], true ) ) {
257                return MediaWikiServices::getInstance()->getUrlUtils()->expand( $path ) ?? false;
258            }
259        }
260
261        return false;
262    }
263
264    /**
265     * Sets modified_time and published_time metadata, if it has not been disabled by setting their values to '-'
266     *
267     * @return void
268     */
269    protected function setModifiedPublishedTime(): void {
270        if ( !isset( $this->metadata['modified_time'] ) && ( $this->metadata['modified_time'] ?? '' ) !== '-' ) {
271            $this->metadata['modified_time'] = $this->getRevisionTimestamp();
272        } elseif ( ( $this->metadata['modified_time'] ?? '' ) === '-' ) {
273            unset( $this->metadata['modified_time'] );
274        }
275
276        if ( !isset( $this->metadata['published_time'] ) && isset( $this->metadata['modified_time'] ) ) {
277            $this->metadata['published_time'] = $this->metadata['modified_time'];
278        }
279    }
280}