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