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