Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
42.35% covered (danger)
42.35%
36 / 85
25.00% covered (danger)
25.00%
2 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialBookSources
42.86% covered (danger)
42.86%
36 / 84
25.00% covered (danger)
25.00%
2 / 8
141.62
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 execute
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 isValidISBN
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
11
 cleanIsbn
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildForm
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
2
 showList
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
30
 makeListItem
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
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 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Specials;
22
23use MediaWiki\Content\TextContent;
24use MediaWiki\Html\Html;
25use MediaWiki\HTMLForm\HTMLForm;
26use MediaWiki\Revision\RevisionLookup;
27use MediaWiki\Revision\SlotRecord;
28use MediaWiki\SpecialPage\SpecialPage;
29use MediaWiki\Title\TitleFactory;
30use UnexpectedValueException;
31
32/**
33 * Information on citing a book with a particular ISBN.
34 *
35 * The parser can create automatic links to this special page when
36 * it sees an ISBN in wikitext.
37 *
38 * @author Rob Church <robchur@gmail.com>
39 * @ingroup SpecialPage
40 */
41class SpecialBookSources extends SpecialPage {
42
43    private RevisionLookup $revisionLookup;
44    private TitleFactory $titleFactory;
45
46    /**
47     * @param RevisionLookup $revisionLookup
48     * @param TitleFactory $titleFactory
49     */
50    public function __construct(
51        RevisionLookup $revisionLookup,
52        TitleFactory $titleFactory
53    ) {
54        parent::__construct( 'Booksources' );
55        $this->revisionLookup = $revisionLookup;
56        $this->titleFactory = $titleFactory;
57    }
58
59    /**
60     * @param string|null $isbn ISBN passed as a subpage parameter
61     */
62    public function execute( $isbn ) {
63        $out = $this->getOutput();
64
65        $this->setHeaders();
66        $this->outputHeader();
67
68        // User provided ISBN
69        $isbn = $isbn ?: $this->getRequest()->getText( 'isbn' );
70        $isbn = trim( $isbn );
71
72        $this->buildForm( $isbn );
73
74        if ( $isbn !== '' ) {
75            if ( !self::isValidISBN( $isbn ) ) {
76                $out->wrapWikiMsg(
77                    "<div class=\"error\">\n$1\n</div>",
78                    'booksources-invalid-isbn'
79                );
80            }
81
82            $this->showList( $isbn );
83        }
84    }
85
86    /**
87     * Return whether a given ISBN (10 or 13) is valid.
88     *
89     * @param string $isbn ISBN passed for check
90     * @return bool
91     */
92    public static function isValidISBN( $isbn ) {
93        $isbn = self::cleanIsbn( $isbn );
94        $sum = 0;
95        if ( strlen( $isbn ) == 13 ) {
96            for ( $i = 0; $i < 12; $i++ ) {
97                if ( $isbn[$i] === 'X' ) {
98                    return false;
99                } elseif ( $i % 2 == 0 ) {
100                    $sum += (int)$isbn[$i];
101                } else {
102                    $sum += 3 * (int)$isbn[$i];
103                }
104            }
105
106            $check = ( 10 - ( $sum % 10 ) ) % 10;
107            if ( (string)$check === $isbn[12] ) {
108                return true;
109            }
110        } elseif ( strlen( $isbn ) == 10 ) {
111            for ( $i = 0; $i < 9; $i++ ) {
112                if ( $isbn[$i] === 'X' ) {
113                    return false;
114                }
115                $sum += (int)$isbn[$i] * ( $i + 1 );
116            }
117
118            $check = $sum % 11;
119            if ( $check == 10 ) {
120                $check = "X";
121            }
122            if ( (string)$check === $isbn[9] ) {
123                return true;
124            }
125        }
126
127        return false;
128    }
129
130    /**
131     * Trim ISBN and remove characters which aren't required
132     *
133     * @param string $isbn Unclean ISBN
134     * @return string
135     */
136    private static function cleanIsbn( $isbn ) {
137        return trim( preg_replace( '![^0-9X]!', '', $isbn ) );
138    }
139
140    /**
141     * Generate a form to allow users to enter an ISBN
142     *
143     * @param string $isbn
144     */
145    private function buildForm( $isbn ) {
146        $formDescriptor = [
147            'isbn' => [
148                'type' => 'text',
149                'name' => 'isbn',
150                'label-message' => 'booksources-isbn',
151                'default' => $isbn,
152                'autofocus' => true,
153                'required' => true,
154            ],
155        ];
156
157        HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
158            ->setTitle( $this->getPageTitle() )
159            ->setWrapperLegendMsg( 'booksources-search-legend' )
160            ->setSubmitTextMsg( 'booksources-search' )
161            ->setMethod( 'get' )
162            ->prepareForm()
163            ->displayForm( false );
164    }
165
166    /**
167     * Determine where to get the list of book sources from,
168     * format and output them
169     *
170     * @param string $isbn
171     * @return bool
172     */
173    private function showList( $isbn ) {
174        $out = $this->getOutput();
175
176        $isbn = self::cleanIsbn( $isbn );
177        # Hook to allow extensions to insert additional HTML,
178        # e.g. for API-interacting plugins and so on
179        $this->getHookRunner()->onBookInformation( $isbn, $out );
180
181        # Check for a local page such as Project:Book_sources and use that if available
182        $page = $this->msg( 'booksources' )->inContentLanguage()->text();
183        // Show list in content language
184        $title = $this->titleFactory->makeTitleSafe( NS_PROJECT, $page );
185        if ( is_object( $title ) && $title->exists() ) {
186            $rev = $this->revisionLookup->getRevisionByTitle( $title );
187            $content = $rev->getContent( SlotRecord::MAIN );
188
189            if ( $content instanceof TextContent ) {
190                // XXX: in the future, this could be stored as structured data, defining a list of book sources
191
192                $text = $content->getText();
193                $out->addWikiTextAsInterface( str_replace( 'MAGICNUMBER', $isbn, $text ) );
194
195                return true;
196            } else {
197                throw new UnexpectedValueException(
198                    "Unexpected content type for book sources: " . $content->getModel()
199                );
200            }
201        }
202
203        # Fall back to the defaults given in the language file
204        $out->addWikiMsg( 'booksources-text' );
205        $out->addHTML( '<ul>' );
206        $items = $this->getContentLanguage()->getBookstoreList();
207        foreach ( $items as $label => $url ) {
208            $out->addHTML( $this->makeListItem( $isbn, $label, $url ) );
209        }
210        $out->addHTML( '</ul>' );
211
212        return true;
213    }
214
215    /**
216     * Format a book source list item
217     *
218     * @param string $isbn
219     * @param string $label Book source label
220     * @param string $url Book source URL
221     * @return string
222     */
223    private function makeListItem( $isbn, $label, $url ) {
224        $url = str_replace( '$1', $isbn, $url );
225
226        return Html::rawElement( 'li', [],
227            Html::element( 'a', [ 'href' => $url, 'class' => 'external' ], $label )
228        );
229    }
230
231    protected function getGroupName() {
232        return 'wiki';
233    }
234}
235
236/** @deprecated class alias since 1.41 */
237class_alias( SpecialBookSources::class, 'SpecialBookSources' );