MediaWiki  master
SpecialFileDuplicateSearch.php
Go to the documentation of this file.
1 <?php
2 
26 namespace MediaWiki\Specials;
27 
28 use File;
29 use HTMLForm;
36 use RepoGroup;
38 
49  private $hash = '';
50 
54  private $filename = '';
55 
59  private $file = null;
60 
61  private LinkBatchFactory $linkBatchFactory;
62  private RepoGroup $repoGroup;
63  private SearchEngineFactory $searchEngineFactory;
64  private ILanguageConverter $languageConverter;
65 
72  public function __construct(
73  LinkBatchFactory $linkBatchFactory,
74  RepoGroup $repoGroup,
75  SearchEngineFactory $searchEngineFactory,
76  LanguageConverterFactory $languageConverterFactory
77  ) {
78  parent::__construct( 'FileDuplicateSearch' );
79  $this->linkBatchFactory = $linkBatchFactory;
80  $this->repoGroup = $repoGroup;
81  $this->searchEngineFactory = $searchEngineFactory;
82  $this->languageConverter = $languageConverterFactory->getLanguageConverter( $this->getContentLanguage() );
83  }
84 
90  private function getDupes() {
91  return $this->repoGroup->findBySha1( $this->hash );
92  }
93 
97  private function showList( $dupes ) {
98  $html = [];
99  $html[] = "<ol class='special'>";
100 
101  foreach ( $dupes as $dupe ) {
102  $line = $this->formatResult( $dupe );
103  $html[] = "<li>" . $line . "</li>";
104  }
105  $html[] = '</ol>';
106 
107  $this->getOutput()->addHTML( implode( "\n", $html ) );
108  }
109 
110  public function execute( $par ) {
111  $this->setHeaders();
112  $this->outputHeader();
113 
114  $this->filename = $par ?? $this->getRequest()->getText( 'filename' );
115  $this->file = null;
116  $this->hash = '';
117  $title = Title::newFromText( $this->filename, NS_FILE );
118  if ( $title && $title->getText() != '' ) {
119  $this->file = $this->repoGroup->findFile( $title );
120  }
121 
122  $out = $this->getOutput();
123 
124  # Create the input form
125  $formFields = [
126  'filename' => [
127  'type' => 'text',
128  'name' => 'filename',
129  'label-message' => 'fileduplicatesearch-filename',
130  'id' => 'filename',
131  'size' => 50,
132  'default' => $this->filename,
133  ],
134  ];
135  $htmlForm = HTMLForm::factory( 'ooui', $formFields, $this->getContext() );
136  $htmlForm->setTitle( $this->getPageTitle() );
137  $htmlForm->setMethod( 'get' );
138  $htmlForm->setSubmitTextMsg( $this->msg( 'fileduplicatesearch-submit' ) );
139 
140  // The form should be visible always, even if it was submitted (e.g. to perform another action).
141  // To bypass the callback validation of HTMLForm, use prepareForm() and displayForm().
142  $htmlForm->prepareForm()->displayForm( false );
143 
144  if ( $this->file ) {
145  $this->hash = $this->file->getSha1();
146  } elseif ( $this->filename !== '' ) {
147  $out->wrapWikiMsg(
148  "<p class='mw-fileduplicatesearch-noresults'>\n$1\n</p>",
149  [ 'fileduplicatesearch-noresults', wfEscapeWikiText( $this->filename ) ]
150  );
151  }
152 
153  if ( $this->hash != '' ) {
154  # Show a thumbnail of the file
155  $img = $this->file;
156  if ( $img ) {
157  $thumb = $img->transform( [ 'width' => 120, 'height' => 120 ] );
158  if ( $thumb ) {
159  $out->addModuleStyles( 'mediawiki.special' );
160  $out->addHTML( '<div id="mw-fileduplicatesearch-icon">' .
161  $thumb->toHtml( [ 'desc-link' => false ] ) . '<br />' .
162  $this->msg( 'fileduplicatesearch-info' )
163  ->numParams( $img->getWidth(), $img->getHeight() )
164  ->sizeParams( $img->getSize() )
165  ->params( $img->getMimeType() )->parseAsBlock() .
166  '</div>' );
167  }
168  }
169 
170  $dupes = $this->getDupes();
171  $numRows = count( $dupes );
172 
173  # Show a short summary
174  if ( $numRows == 1 ) {
175  $out->wrapWikiMsg(
176  "<p class='mw-fileduplicatesearch-result-1'>\n$1\n</p>",
177  [ 'fileduplicatesearch-result-1', wfEscapeWikiText( $this->filename ) ]
178  );
179  } elseif ( $numRows ) {
180  $out->wrapWikiMsg(
181  "<p class='mw-fileduplicatesearch-result-n'>\n$1\n</p>",
182  [ 'fileduplicatesearch-result-n', wfEscapeWikiText( $this->filename ),
183  $this->getLanguage()->formatNum( $numRows - 1 ) ]
184  );
185  }
186 
187  $this->doBatchLookups( $dupes );
188  $this->showList( $dupes );
189  }
190  }
191 
195  private function doBatchLookups( $list ) {
196  $batch = $this->linkBatchFactory->newLinkBatch();
197  foreach ( $list as $file ) {
198  $batch->addObj( $file->getTitle() );
199  if ( $file->isLocal() ) {
200  $uploader = $file->getUploader( File::FOR_THIS_USER, $this->getAuthority() );
201  if ( $uploader ) {
202  $batch->add( NS_USER, $uploader->getName() );
203  $batch->add( NS_USER_TALK, $uploader->getName() );
204  }
205  }
206  }
207 
208  $batch->execute();
209  }
210 
215  private function formatResult( $result ) {
216  $linkRenderer = $this->getLinkRenderer();
217  $nt = $result->getTitle();
218  $text = $this->languageConverter->convert( $nt->getText() );
219  $plink = $linkRenderer->makeLink(
220  $nt,
221  $text
222  );
223 
224  $uploader = $result->getUploader( File::FOR_THIS_USER, $this->getAuthority() );
225  if ( $result->isLocal() && $uploader ) {
226  $user = Linker::userLink( $uploader->getId(), $uploader->getName() );
227  $user .= '<span style="white-space: nowrap;">';
228  $user .= Linker::userToolLinks( $uploader->getId(), $uploader->getName() );
229  $user .= '</span>';
230  } elseif ( $uploader ) {
231  $user = htmlspecialchars( $uploader->getName() );
232  } else {
233  $user = '<span class="history-deleted">'
234  . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
235  }
236 
237  $time = htmlspecialchars( $this->getLanguage()->userTimeAndDate(
238  $result->getTimestamp(), $this->getUser() ) );
239 
240  return "$plink . . $user . . $time";
241  }
242 
251  public function prefixSearchSubpages( $search, $limit, $offset ) {
252  $title = Title::newFromText( $search, NS_FILE );
253  if ( !$title || $title->getNamespace() !== NS_FILE ) {
254  // No prefix suggestion outside of file namespace
255  return [];
256  }
257  $searchEngine = $this->searchEngineFactory->create();
258  $searchEngine->setLimitOffset( $limit, $offset );
259  // Autocomplete subpage the same as a normal search, but just for files
260  $searchEngine->setNamespaces( [ NS_FILE ] );
261  $result = $searchEngine->defaultPrefixSearch( $search );
262 
263  return array_map( static function ( Title $t ) {
264  // Remove namespace in search suggestion
265  return $t->getText();
266  }, $result );
267  }
268 
269  protected function getGroupName() {
270  return 'media';
271  }
272 }
273 
277 class_alias( SpecialFileDuplicateSearch::class, 'SpecialFileDuplicateSearch' );
const NS_USER
Definition: Defines.php:66
const NS_FILE
Definition: Defines.php:70
const NS_USER_TALK
Definition: Defines.php:67
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:70
const FOR_THIS_USER
Definition: File.php:91
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition: HTMLForm.php:158
static factory( $displayFormat, $descriptor, IContextSource $context, $messagePrefix='')
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:360
An interface for creating language converters.
getLanguageConverter( $language=null)
Provide a LanguageConverter for given language.
Some internal bits split of from Skin.php.
Definition: Linker.php:65
static userToolLinks( $userId, $userText, $redContribsWhenNoEdits=false, $flags=0, $edits=null, $useParentheses=true)
Generate standard user tool links (talk, contributions, block link, etc.)
Definition: Linker.php:1242
static userLink( $userId, $userName, $altUserName=false, $attributes=[])
Make user link (or user contributions for unregistered users)
Definition: Linker.php:1184
Parent class for all special pages.
Definition: SpecialPage.php:65
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getPageTitle( $subpage=false)
Get a self-referential title object.
getContext()
Gets the context this SpecialPage is executed in.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getAuthority()
Shortcut to get the Authority executing this instance.
getContentLanguage()
Shortcut to get content language.
getLanguage()
Shortcut to get user's language.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
Searches the database for files of the requested hash, comparing this with the 'img_sha1' field in th...
execute( $par)
Default execute method Checks user permissions.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
__construct(LinkBatchFactory $linkBatchFactory, RepoGroup $repoGroup, SearchEngineFactory $searchEngineFactory, LanguageConverterFactory $languageConverterFactory)
Represents a title within MediaWiki.
Definition: Title.php:76
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:400
Prioritized list of file repositories.
Definition: RepoGroup.php:30
Factory class for SearchEngine.
The shared interface for all language converters.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42