]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/Category.php
MediaWiki 1.17.0
[autoinstalls/mediawiki.git] / includes / Category.php
1 <?php
2 /**
3  * Category objects are immutable, strictly speaking. If you call methods that change the database,
4  * like to refresh link counts, the objects will be appropriately reinitialized.
5  * Member variables are lazy-initialized.
6  *
7  * TODO: Move some stuff from CategoryPage.php to here, and use that.
8  *
9  * @author Simetrical
10  */
11
12 class Category {
13         /** Name of the category, normalized to DB-key form */
14         private $mName = null;
15         private $mID = null;
16         /** Category page title */
17         private $mTitle = null;
18         /** Counts of membership (cat_pages, cat_subcats, cat_files) */
19         private $mPages = null, $mSubcats = null, $mFiles = null;
20
21         private function __construct() { }
22
23         /**
24          * Set up all member variables using a database query.
25          * @return bool True on success, false on failure.
26          */
27         protected function initialize() {
28                 if ( $this->mName === null && $this->mID === null ) {
29                         throw new MWException( __METHOD__ . ' has both names and IDs null' );
30                 } elseif ( $this->mID === null ) {
31                         $where = array( 'cat_title' => $this->mName );
32                 } elseif ( $this->mName === null ) {
33                         $where = array( 'cat_id' => $this->mID );
34                 } else {
35                         # Already initialized
36                         return true;
37                 }
38                 $dbr = wfGetDB( DB_SLAVE );
39                 $row = $dbr->selectRow(
40                         'category',
41                         array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files' ),
42                         $where,
43                         __METHOD__
44                 );
45
46                 if ( !$row ) {
47                         # Okay, there were no contents.  Nothing to initialize.
48                         if ( $this->mTitle ) {
49                                 # If there is a title object but no record in the category table, treat this as an empty category
50                                 $this->mID      = false;
51                                 $this->mName    = $this->mTitle->getDBkey();
52                                 $this->mPages   = 0;
53                                 $this->mSubcats = 0;
54                                 $this->mFiles   = 0;
55
56                                 return true;
57                         } else {
58                                 return false; # Fail
59                         }
60                 }
61
62                 $this->mID      = $row->cat_id;
63                 $this->mName    = $row->cat_title;
64                 $this->mPages   = $row->cat_pages;
65                 $this->mSubcats = $row->cat_subcats;
66                 $this->mFiles   = $row->cat_files;
67
68                 # (bug 13683) If the count is negative, then 1) it's obviously wrong
69                 # and should not be kept, and 2) we *probably* don't have to scan many
70                 # rows to obtain the correct figure, so let's risk a one-time recount.
71                 if ( $this->mPages < 0 || $this->mSubcats < 0 || $this->mFiles < 0 ) {
72                         $this->refreshCounts();
73                 }
74
75                 return true;
76         }
77
78         /**
79          * Factory function.
80          *
81          * @param $name Array: A category name (no "Category:" prefix).  It need
82          *   not be normalized, with spaces replaced by underscores.
83          * @return mixed Category, or false on a totally invalid name
84          */
85         public static function newFromName( $name ) {
86                 $cat = new self();
87                 $title = Title::makeTitleSafe( NS_CATEGORY, $name );
88
89                 if ( !is_object( $title ) ) {
90                         return false;
91                 }
92
93                 $cat->mTitle = $title;
94                 $cat->mName = $title->getDBkey();
95
96                 return $cat;
97         }
98
99         /**
100          * Factory function.
101          *
102          * @param $title Title for the category page
103          * @return Mixed: category, or false on a totally invalid name
104          */
105         public static function newFromTitle( $title ) {
106                 $cat = new self();
107
108                 $cat->mTitle = $title;
109                 $cat->mName = $title->getDBkey();
110
111                 return $cat;
112         }
113
114         /**
115          * Factory function.
116          *
117          * @param $id Integer: a category id
118          * @return Category
119          */
120         public static function newFromID( $id ) {
121                 $cat = new self();
122                 $cat->mID = intval( $id );
123                 return $cat;
124         }
125
126         /**
127          * Factory function, for constructing a Category object from a result set
128          *
129          * @param $row result set row, must contain the cat_xxx fields. If the fields are null,
130          *        the resulting Category object will represent an empty category if a title object
131          *        was given. If the fields are null and no title was given, this method fails and returns false.
132          * @param $title optional title object for the category represented by the given row.
133          *        May be provided if it is already known, to avoid having to re-create a title object later.
134          * @return Category
135          */
136         public static function newFromRow( $row, $title = null ) {
137                 $cat = new self();
138                 $cat->mTitle = $title;
139
140                 # NOTE: the row often results from a LEFT JOIN on categorylinks. This may result in
141                 #       all the cat_xxx fields being null, if the category page exists, but nothing
142                 #       was ever added to the category. This case should be treated linke an empty
143                 #       category, if possible.
144
145                 if ( $row->cat_title === null ) {
146                         if ( $title === null ) {
147                                 # the name is probably somewhere in the row, for example as page_title,
148                                 # but we can't know that here...
149                                 return false;
150                         } else {
151                                 $cat->mName = $title->getDBkey(); # if we have a title object, fetch the category name from there
152                         }
153
154                         $cat->mID =   false;
155                         $cat->mSubcats = 0;
156                         $cat->mPages   = 0;
157                         $cat->mFiles   = 0;
158                 } else {
159                         $cat->mName    = $row->cat_title;
160                         $cat->mID      = $row->cat_id;
161                         $cat->mSubcats = $row->cat_subcats;
162                         $cat->mPages   = $row->cat_pages;
163                         $cat->mFiles   = $row->cat_files;
164                 }
165
166                 return $cat;
167         }
168
169         /** @return mixed DB key name, or false on failure */
170         public function getName() { return $this->getX( 'mName' ); }
171
172         /** @return mixed Category ID, or false on failure */
173         public function getID() { return $this->getX( 'mID' ); }
174
175         /** @return mixed Total number of member pages, or false on failure */
176         public function getPageCount() { return $this->getX( 'mPages' ); }
177
178         /** @return mixed Number of subcategories, or false on failure */
179         public function getSubcatCount() { return $this->getX( 'mSubcats' ); }
180
181         /** @return mixed Number of member files, or false on failure */
182         public function getFileCount() { return $this->getX( 'mFiles' ); }
183
184         /**
185          * @return mixed The Title for this category, or false on failure.
186          */
187         public function getTitle() {
188                 if ( $this->mTitle ) return $this->mTitle;
189
190                 if ( !$this->initialize() ) {
191                         return false;
192                 }
193
194                 $this->mTitle = Title::makeTitleSafe( NS_CATEGORY, $this->mName );
195                 return $this->mTitle;
196         }
197
198         /**
199          * Fetch a TitleArray of up to $limit category members, beginning after the
200          * category sort key $offset.
201          * @param $limit integer
202          * @param $offset string
203          * @return TitleArray object for category members.
204          */
205         public function getMembers( $limit = false, $offset = '' ) {
206                 $dbr = wfGetDB( DB_SLAVE );
207
208                 $conds = array( 'cl_to' => $this->getName(), 'cl_from = page_id' );
209                 $options = array( 'ORDER BY' => 'cl_sortkey' );
210
211                 if ( $limit ) {
212                         $options[ 'LIMIT' ] = $limit;
213                 }
214
215                 if ( $offset !== '' ) {
216                         $conds[] = 'cl_sortkey > ' . $dbr->addQuotes( $offset );
217                 }
218
219                 return TitleArray::newFromResult(
220                         $dbr->select(
221                                 array( 'page', 'categorylinks' ),
222                                 array( 'page_id', 'page_namespace', 'page_title', 'page_len',
223                                         'page_is_redirect', 'page_latest' ),
224                                 $conds,
225                                 __METHOD__,
226                                 $options
227                         )
228                 );
229         }
230
231         /** Generic accessor */
232         private function getX( $key ) {
233                 if ( !$this->initialize() ) {
234                         return false;
235                 }
236                 return $this-> { $key } ;
237         }
238
239         /**
240          * Refresh the counts for this category.
241          *
242          * @return bool True on success, false on failure
243          */
244         public function refreshCounts() {
245                 if ( wfReadOnly() ) {
246                         return false;
247                 }
248
249                 # Note, we must use names for this, since categorylinks does.
250                 if ( $this->mName === null ) {
251                         if ( !$this->initialize() ) {
252                                 return false;
253                         }
254                 }
255
256                 $dbw = wfGetDB( DB_MASTER );
257                 $dbw->begin();
258
259                 # Insert the row if it doesn't exist yet (e.g., this is being run via
260                 # update.php from a pre-1.16 schema).  TODO: This will cause lots and
261                 # lots of gaps on some non-MySQL DBMSes if you run populateCategory.php
262                 # repeatedly.  Plus it's an extra query that's unneeded almost all the
263                 # time.  This should be rewritten somehow, probably.
264                 $seqVal = $dbw->nextSequenceValue( 'category_cat_id_seq' );
265                 $dbw->insert(
266                         'category',
267                         array(
268                                 'cat_id' => $seqVal,
269                                 'cat_title' => $this->mName
270                         ),
271                         __METHOD__,
272                         'IGNORE'
273                 );
274
275                 $cond1 = $dbw->conditional( 'page_namespace=' . NS_CATEGORY, 1, 'NULL' );
276                 $cond2 = $dbw->conditional( 'page_namespace=' . NS_FILE, 1, 'NULL' );
277                 $result = $dbw->selectRow(
278                         array( 'categorylinks', 'page' ),
279                         array( 'COUNT(*) AS pages',
280                                    "COUNT($cond1) AS subcats",
281                                    "COUNT($cond2) AS files"
282                         ),
283                         array( 'cl_to' => $this->mName, 'page_id = cl_from' ),
284                         __METHOD__,
285                         'LOCK IN SHARE MODE'
286                 );
287                 $ret = $dbw->update(
288                         'category',
289                         array(
290                                 'cat_pages' => $result->pages,
291                                 'cat_subcats' => $result->subcats,
292                                 'cat_files' => $result->files
293                         ),
294                         array( 'cat_title' => $this->mName ),
295                         __METHOD__
296                 );
297                 $dbw->commit();
298
299                 # Now we should update our local counts.
300                 $this->mPages   = $result->pages;
301                 $this->mSubcats = $result->subcats;
302                 $this->mFiles   = $result->files;
303
304                 return $ret;
305         }
306 }