55 protected function initialize( $mode = self::LOAD_ONLY ) {
56 if ( $this->mName ===
null && $this->mID ===
null ) {
57 throw new MWException( __METHOD__ .
' has both names and IDs null' );
58 } elseif ( $this->mID ===
null ) {
59 $where = [
'cat_title' => $this->mName ];
60 } elseif ( $this->mName ===
null ) {
61 $where = [
'cat_id' => $this->mID ];
68 $row =
$dbr->selectRow(
70 [
'cat_id',
'cat_title',
'cat_pages',
'cat_subcats',
'cat_files' ],
76 # Okay, there were no contents. Nothing to initialize.
77 if ( $this->mTitle ) {
78 # If there is a title object but no record in the category table,
79 # treat this as an empty category.
81 $this->mName = $this->mTitle->getDBkey();
86 # If the title exists, call refreshCounts to add a row for it.
87 if ( $mode === self::LAZY_INIT_ROW && $this->mTitle->exists() ) {
88 DeferredUpdates::addCallableUpdate( [ $this,
'refreshCounts' ] );
97 $this->mID = $row->cat_id;
98 $this->mName = $row->cat_title;
99 $this->mPages = $row->cat_pages;
100 $this->mSubcats = $row->cat_subcats;
101 $this->mFiles = $row->cat_files;
103 # (T15683) If the count is negative, then 1) it's obviously wrong
104 # and should not be kept, and 2) we *probably* don't have to scan many
105 # rows to obtain the correct figure, so let's risk a one-time recount.
106 if ( $this->mPages < 0 || $this->mSubcats < 0 || $this->mFiles < 0 ) {
107 $this->mPages = max( $this->mPages, 0 );
108 $this->mSubcats = max( $this->mSubcats, 0 );
109 $this->mFiles = max( $this->mFiles, 0 );
111 if ( $mode === self::LAZY_INIT_ROW ) {
112 DeferredUpdates::addCallableUpdate( [ $this,
'refreshCounts' ] );
130 if ( !is_object(
$title ) ) {
135 $cat->mName =
$title->getDBkey();
150 $cat->mName =
$title->getDBkey();
163 $cat->mID = intval( $id );
183 # NOTE: the row often results from a LEFT JOIN on categorylinks. This may result in
184 # all the cat_xxx fields being null, if the category page exists, but nothing
185 # was ever added to the category. This case should be treated link an empty
186 # category, if possible.
188 if ( $row->cat_title ===
null ) {
190 # the name is probably somewhere in the row, for example as page_title,
191 # but we can't know that here...
194 # if we have a title object, fetch the category name from there
195 $cat->mName =
$title->getDBkey();
203 $cat->mName = $row->cat_title;
204 $cat->mID = $row->cat_id;
205 $cat->mSubcats = $row->cat_subcats;
206 $cat->mPages = $row->cat_pages;
207 $cat->mFiles = $row->cat_files;
217 return $this->
getX(
'mName' );
224 return $this->
getX(
'mID' );
231 return $this->
getX(
'mPages' );
238 return $this->
getX(
'mSubcats' );
245 return $this->
getX(
'mFiles' );
252 if ( $this->mTitle ) {
256 if ( !$this->
initialize( self::LAZY_INIT_ROW ) ) {
260 $this->mTitle = Title::makeTitleSafe(
NS_CATEGORY, $this->mName );
274 $conds = [
'cl_to' => $this->
getName(),
'cl_from = page_id' ];
275 $options = [
'ORDER BY' =>
'cl_sortkey' ];
278 $options[
'LIMIT'] = $limit;
281 if ( $offset !==
'' ) {
282 $conds[] =
'cl_sortkey > ' .
$dbr->addQuotes( $offset );
287 [
'page',
'categorylinks' ],
288 [
'page_id',
'page_namespace',
'page_title',
'page_len',
289 'page_is_redirect',
'page_latest' ],
304 private function getX( $key ) {
305 if ( $this->{$key} ===
null && !$this->
initialize( self::LAZY_INIT_ROW ) ) {
308 return $this->{$key};
321 # If we have just a category name, find out whether there is an
322 # existing row. Or if we have just an ID, get the name, because
323 # that's what categorylinks uses.
324 if ( !$this->
initialize( self::LOAD_ONLY ) ) {
329 # Avoid excess contention on the same category (T162121)
330 $name = __METHOD__ .
':' . md5( $this->mName );
331 $scopedLock = $dbw->getScopedLockAndFlush( $name, __METHOD__, 0 );
332 if ( !$scopedLock ) {
336 $dbw->startAtomic( __METHOD__ );
340 $dbw->lockForUpdate(
'category', [
'cat_title' => $this->mName ], __METHOD__ );
344 $dbw->selectRowCount(
345 [
'categorylinks',
'page' ],
347 [
'cl_to' => $this->mName,
'page_id = cl_from' ],
349 [
'LOCK IN SHARE MODE' ]
352 $catCond = $dbw->conditional( [
'page_namespace' =>
NS_CATEGORY ], 1,
'NULL' );
353 $fileCond = $dbw->conditional( [
'page_namespace' =>
NS_FILE ], 1,
'NULL' );
354 $result = $dbw->selectRow(
355 [
'categorylinks',
'page' ],
357 'pages' =>
'COUNT(*)',
358 'subcats' =>
"COUNT($catCond)",
359 'files' =>
"COUNT($fileCond)"
361 [
'cl_to' => $this->mName,
'page_id = cl_from' ],
365 $shouldExist = $result->pages > 0 || $this->
getTitle()->exists();
368 if ( $shouldExist ) {
369 # The category row already exists, so do a plain UPDATE instead
370 # of INSERT...ON DUPLICATE KEY UPDATE to avoid creating a gap
371 # in the cat_id sequence. The row may or may not be "affected".
375 'cat_pages' => $result->pages,
376 'cat_subcats' => $result->subcats,
377 'cat_files' => $result->files
379 [
'cat_title' => $this->mName ],
383 # The category is empty and has no description page, delete it
386 [
'cat_title' => $this->mName ],
391 } elseif ( $shouldExist ) {
392 # The category row doesn't exist but should, so create it. Use
393 # upsert in case of races.
397 'cat_title' => $this->mName,
398 'cat_pages' => $result->pages,
399 'cat_subcats' => $result->subcats,
400 'cat_files' => $result->files
404 'cat_pages' => $result->pages,
405 'cat_subcats' => $result->subcats,
406 'cat_files' => $result->files
414 $dbw->endAtomic( __METHOD__ );
416 # Now we should update our local counts.
417 $this->mPages = $result->pages;
418 $this->mSubcats = $result->subcats;
419 $this->mFiles = $result->files;
454 $dbw->startAtomic( __METHOD__ );
456 $typeOccurances = $dbw->selectFieldValues(
459 [
'cl_to' => $this->
getName() ],
461 [
'LIMIT' => $maxSize + 1 ]
464 if ( !$typeOccurances ) {
466 } elseif ( count( $typeOccurances ) <= $maxSize ) {
467 $countByType = array_count_values( $typeOccurances );
468 $doRefresh = !$dbw->selectField(
472 'cat_title' => $this->
getName(),
473 'cat_pages' => $countByType[
'page'] ?? 0,
474 'cat_subcats' => $countByType[
'subcat'] ?? 0,
475 'cat_files' => $countByType[
'file'] ?? 0
483 $dbw->endAtomic( __METHOD__ );
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Category objects are immutable, strictly speaking.
static newFromID( $id)
Factory function.
static newFromName( $name)
Factory function.
refreshCountsIfEmpty()
Call refreshCounts() if there are no entries in the categorylinks table or if the category table has ...
$mName
Name of the category, normalized to DB-key form.
static newFromTitle( $title)
Factory function.
initialize( $mode=self::LOAD_ONLY)
Set up all member variables using a database query.
$mPages
Counts of membership (cat_pages, cat_subcats, cat_files)
getMembers( $limit=false, $offset='')
Fetch a TitleArray of up to $limit category members, beginning after the category sort key $offset.
refreshCounts()
Refresh the counts for this category.
getX( $key)
Generic accessor.
refreshCountsIfSmall( $maxSize=self::ROW_COUNT_SMALL)
Call refreshCounts() if there are few entries in the categorylinks table.
static newFromRow( $row, $title=null)
Factory function, for constructing a Category object from a result set.
static newFromResult( $res)
Represents a title within MediaWiki.