Index: trunk/extensions/Favorites/FavParser.php |
— | — | @@ -0,0 +1,266 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class FavParser { |
| 5 | + |
| 6 | +function wfSpecialFavoritelist() { |
| 7 | + |
| 8 | + global $wgUser, $wgOut, $wgLang, $wgRequest; |
| 9 | + global $wgRCShowFavoritingUsers, $wgEnotifFavoritelist, $wgShowUpdatedMarker; |
| 10 | + $output = ''; |
| 11 | + |
| 12 | + $skin = $wgUser->getSkin(); |
| 13 | + $specialTitle = SpecialPage::getTitleFor( 'Favoritelist' ); |
| 14 | + //$wgOut->setRobotPolicy( 'noindex,nofollow' ); |
| 15 | + |
| 16 | + # Anons don't get a favoritelist |
| 17 | + if( $wgUser->isAnon() ) { |
| 18 | + //$wgOut->setPageTitle( wfMsg( 'favoritenologin' ) ); |
| 19 | + $llink = $skin->linkKnown( |
| 20 | + SpecialPage::getTitleFor( 'Userlogin' ), |
| 21 | + wfMsg( 'loginreqlink' ), |
| 22 | + array(), |
| 23 | + array( 'returnto' => $specialTitle->getPrefixedText() ) |
| 24 | + ); |
| 25 | + $output = wfMsgHtml( 'favoritelistanontext', $llink ) ; |
| 26 | + return $output ; |
| 27 | + |
| 28 | + } |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | + |
| 33 | + $output = $this->viewFavList($wgUser, $output, $wgRequest); |
| 34 | + return $output ; |
| 35 | +} |
| 36 | + |
| 37 | + |
| 38 | + private function viewFavList ($user, $output, $request) { |
| 39 | + global $wgUser, $wgOut, $wgLang, $wgRequest; |
| 40 | + $uid = $wgUser->getId(); |
| 41 | + $output = $this->showNormalForm( $output, $user ); |
| 42 | + |
| 43 | + $dbr = wfGetDB( DB_SLAVE, 'favoritelist' ); |
| 44 | + |
| 45 | + |
| 46 | + $favoritelistCount = $dbr->selectField( 'favoritelist', 'COUNT(*)', |
| 47 | + array( 'fl_user' => $uid ), __METHOD__ ); |
| 48 | + // Adjust for page X, talk:page X, which are both stored separately, |
| 49 | + // but treated together |
| 50 | + $nitems = floor($favoritelistCount / 2); |
| 51 | + |
| 52 | + if( $nitems == 0 ) { |
| 53 | + $output = wfmsg('nofavoritelist'); |
| 54 | + |
| 55 | + } |
| 56 | + return $output; |
| 57 | +} |
| 58 | + |
| 59 | + |
| 60 | + /** |
| 61 | + * Extract a list of titles from a blob of text, returning |
| 62 | + * (prefixed) strings; unfavoritable titles are ignored |
| 63 | + * |
| 64 | + * @param $list mixed |
| 65 | + * @return array |
| 66 | + */ |
| 67 | + private function extractTitles( $list ) { |
| 68 | + $titles = array(); |
| 69 | + if( !is_array( $list ) ) { |
| 70 | + $list = explode( "\n", trim( $list ) ); |
| 71 | + if( !is_array( $list ) ) |
| 72 | + return array(); |
| 73 | + } |
| 74 | + foreach( $list as $text ) { |
| 75 | + $text = trim( $text ); |
| 76 | + if( strlen( $text ) > 0 ) { |
| 77 | + $title = Title::newFromText( $text ); |
| 78 | + //if( $title instanceof Title && $title->isFavoritable() ) |
| 79 | + $titles[] = $title->getPrefixedText(); |
| 80 | + } |
| 81 | + } |
| 82 | + return array_unique( $titles ); |
| 83 | + } |
| 84 | + |
| 85 | + |
| 86 | + /** |
| 87 | + * Count the number of titles on a user's favoritelist, excluding talk pages |
| 88 | + * |
| 89 | + * @param $user User |
| 90 | + * @return int |
| 91 | + */ |
| 92 | + private function countFavoritelist( $user ) { |
| 93 | + $dbr = wfGetDB( DB_MASTER ); |
| 94 | + $res = $dbr->select( 'favoritelist', 'COUNT(*) AS count', array( 'fl_user' => $user->getId() ), __METHOD__ ); |
| 95 | + $row = $dbr->fetchObject( $res ); |
| 96 | + return ceil( $row->count / 2 ); // Paranoia |
| 97 | + } |
| 98 | + |
| 99 | + /** |
| 100 | + * Prepare a list of titles on a user's favoritelist (excluding talk pages) |
| 101 | + * and return an array of (prefixed) strings |
| 102 | + * |
| 103 | + * @param $user User |
| 104 | + * @return array |
| 105 | + */ |
| 106 | + private function getFavoritelist( $user ) { |
| 107 | + $list = array(); |
| 108 | + $dbr = wfGetDB( DB_MASTER ); |
| 109 | + $res = $dbr->select( |
| 110 | + 'favoritelist', |
| 111 | + '*', |
| 112 | + array( |
| 113 | + 'fl_user' => $user->getId(), |
| 114 | + ), |
| 115 | + __METHOD__ |
| 116 | + ); |
| 117 | + if( $res->numRows() > 0 ) { |
| 118 | + while( $row = $res->fetchObject() ) { |
| 119 | + $title = Title::makeTitleSafe( $row->fl_namespace, $row->fl_title ); |
| 120 | + if( $title instanceof Title && !$title->isTalkPage() ) |
| 121 | + $list[] = $title->getPrefixedText(); |
| 122 | + } |
| 123 | + $res->free(); |
| 124 | + } |
| 125 | + return $list; |
| 126 | + } |
| 127 | + |
| 128 | + /** |
| 129 | + * Get a list of titles on a user's favoritelist, excluding talk pages, |
| 130 | + * and return as a two-dimensional array with namespace, title and |
| 131 | + * redirect status |
| 132 | + * |
| 133 | + * @param $user User |
| 134 | + * @return array |
| 135 | + */ |
| 136 | + private function getFavoritelistInfo( $user ) { |
| 137 | + $titles = array(); |
| 138 | + $dbr = wfGetDB( DB_MASTER ); |
| 139 | + $uid = intval( $user->getId() ); |
| 140 | + list( $favoritelist, $page ) = $dbr->tableNamesN( 'favoritelist', 'page' ); |
| 141 | + $sql = "SELECT fl_namespace, fl_title, page_id, page_len, page_is_redirect |
| 142 | + FROM {$favoritelist} LEFT JOIN {$page} ON ( fl_namespace = page_namespace |
| 143 | + AND fl_title = page_title ) WHERE fl_user = {$uid}"; |
| 144 | + $res = $dbr->query( $sql, __METHOD__ ); |
| 145 | + if( $res && $dbr->numRows( $res ) > 0 ) { |
| 146 | + $cache = LinkCache::singleton(); |
| 147 | + while( $row = $dbr->fetchObject( $res ) ) { |
| 148 | + $title = Title::makeTitleSafe( $row->fl_namespace, $row->fl_title ); |
| 149 | + if( $title instanceof Title ) { |
| 150 | + // Update the link cache while we're at it |
| 151 | + if( $row->page_id ) { |
| 152 | + $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect ); |
| 153 | + } else { |
| 154 | + $cache->addBadLinkObj( $title ); |
| 155 | + } |
| 156 | + // Ignore non-talk |
| 157 | + if( !$title->isTalkPage() ) |
| 158 | + $titles[$row->fl_namespace][$row->fl_title] = $row->page_is_redirect; |
| 159 | + } |
| 160 | + } |
| 161 | + } |
| 162 | + return $titles; |
| 163 | + } |
| 164 | + |
| 165 | + /** |
| 166 | + * Show a message indicating the number of items on the user's favoritelist, |
| 167 | + * and return this count for additional checking |
| 168 | + * |
| 169 | + * @param $output OutputPage |
| 170 | + * @param $user User |
| 171 | + * @return int |
| 172 | + */ |
| 173 | + private function showItemCount( $output, $user ) { |
| 174 | + if( ( $count = $this->countFavoritelist( $user ) ) > 0 ) { |
| 175 | + //$output->addHTML( wfMsgExt( 'favoritelistedit-numitems', 'parse', |
| 176 | + // $GLOBALS['wgLang']->formatNum( $count ) ) ); |
| 177 | + } else { |
| 178 | + //$output->addHTML( wfMsg( 'favoritelistedit-noitems', 'parse' ) ); |
| 179 | + } |
| 180 | + return $count; |
| 181 | + } |
| 182 | + |
| 183 | + /** |
| 184 | + * Remove all titles from a user's favoritelist |
| 185 | + * |
| 186 | + * @param $user User |
| 187 | +// */ |
| 188 | + |
| 189 | + |
| 190 | + /** |
| 191 | + * Show the standard favoritelist |
| 192 | + * |
| 193 | + * @param $output OutputPage |
| 194 | + * @param $user User |
| 195 | + */ |
| 196 | + private function showNormalForm( $output, $user ) { |
| 197 | + global $wgUser; |
| 198 | + if( ( $count = $this->showItemCount( $output, $user ) ) > 0 ) { |
| 199 | + $form = $this->buildRemoveList( $user, $wgUser->getSkin() ); |
| 200 | + $output .= $form ; |
| 201 | + return $output; |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + /** |
| 206 | + * Build part of the standard favoritelist |
| 207 | + * |
| 208 | + * @param $user User |
| 209 | + * @param $skin Skin (really, Linker) |
| 210 | + */ |
| 211 | + private function buildRemoveList( $user, $skin ) { |
| 212 | + $list = ""; |
| 213 | + foreach( $this->getFavoritelistInfo( $user ) as $namespace => $pages ) { |
| 214 | + |
| 215 | + $list .= "<ul>\n"; |
| 216 | + foreach( $pages as $dbkey => $redirect ) { |
| 217 | + $title = Title::makeTitleSafe( $namespace, $dbkey ); |
| 218 | + $list .= $this->buildRemoveLine( $title, $redirect, $skin ); |
| 219 | + } |
| 220 | + $list .= "</ul>\n"; |
| 221 | + } |
| 222 | + return $list; |
| 223 | + } |
| 224 | + |
| 225 | + |
| 226 | + |
| 227 | + /** |
| 228 | + * Build a single list item containing a link |
| 229 | + * |
| 230 | + * @param $title Title |
| 231 | + * @param $redirect bool |
| 232 | + * @param $skin Skin |
| 233 | + * @return string |
| 234 | + */ |
| 235 | + private function buildRemoveLine( $title, $redirect, $skin ) { |
| 236 | + global $wgLang; |
| 237 | + |
| 238 | + $link = $skin->link( $title ); |
| 239 | + if( $redirect ) |
| 240 | + $link = '<span class="favoritelistredir">' . $link . '</span>'; |
| 241 | + |
| 242 | + return "<li>" . $link . "</li>\n"; |
| 243 | + } |
| 244 | + |
| 245 | +} |
| 246 | +/** |
| 247 | + * Count the number of items on a user's favoritelist |
| 248 | + * |
| 249 | + * @param $talk Include talk pages |
| 250 | + * @return integer |
| 251 | + */ |
| 252 | +function flCountItems( &$user, $talk = true ) { |
| 253 | + $dbr = wfGetDB( DB_SLAVE, 'favoritelist' ); |
| 254 | + |
| 255 | + # Fetch the raw count |
| 256 | + $res = $dbr->select( 'favoritelist', 'COUNT(*) AS count', |
| 257 | + array( 'fl_user' => $user->mId ), 'flCountItems' ); |
| 258 | + $row = $dbr->fetchObject( $res ); |
| 259 | + $count = $row->count; |
| 260 | + $dbr->freeResult( $res ); |
| 261 | + |
| 262 | + # Halve to remove talk pages if needed |
| 263 | + if( !$talk ) |
| 264 | + $count = floor( $count / 2 ); |
| 265 | + |
| 266 | + return( $count ); |
| 267 | +} |
Property changes on: trunk/extensions/Favorites/FavParser.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 268 | + native |
Index: trunk/extensions/Favorites/favorites.sql |
— | — | @@ -0,0 +1,13 @@ |
| 2 | +-- |
| 3 | +-- Table structure for table `favoritelist` |
| 4 | +-- |
| 5 | +-- Replace /*_*/ with the proper prefix |
| 6 | + |
| 7 | +CREATE TABLE IF NOT EXISTS /*_*/favoritelist ( |
| 8 | + fl_user int(10) unsigned NOT NULL, |
| 9 | + fl_namespace int(11) NOT NULL DEFAULT '0', |
| 10 | + fl_title varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '', |
| 11 | + fl_notificationtimestamp varbinary(14) DEFAULT NULL, |
| 12 | + UNIQUE KEY fl_user (fl_user,fl_namespace,fl_title), |
| 13 | + KEY namespace_title (fl_namespace,fl_title) |
| 14 | +) |
Property changes on: trunk/extensions/Favorites/favorites.sql |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 15 | + native |
Index: trunk/extensions/Favorites/FavUser.php |
— | — | @@ -0,0 +1,36 @@ |
| 2 | +<?php |
| 3 | +class FavUser extends User { |
| 4 | + /** |
| 5 | + * Check the Favorited status of an article. |
| 6 | + * @param $title \type{Title} Title of the article to look at |
| 7 | + * @return \bool True if article is Favorited |
| 8 | + */ |
| 9 | + function isFavorited( $title ) { |
| 10 | + global $wgUser; |
| 11 | + $fl = FavoritedItem::fromUserTitle( $wgUser, $title ); |
| 12 | + return $fl->isFavorited(); |
| 13 | + |
| 14 | + } |
| 15 | + |
| 16 | + /** |
| 17 | + * Favorite an article. |
| 18 | + * @param $title \type{Title} Title of the article to look at |
| 19 | + */ |
| 20 | + function addFavorite( $title ) { |
| 21 | + global $wgUser; |
| 22 | + $fl = FavoritedItem::fromUserTitle( $wgUser, $title ); |
| 23 | + $fl->addFavorite(); |
| 24 | + $title->invalidateCache(); |
| 25 | + } |
| 26 | + |
| 27 | + /** |
| 28 | + * Stop Favoriting an article. |
| 29 | + * @param $title \type{Title} Title of the article to look at |
| 30 | + */ |
| 31 | + function removeFavorite( $title ) { |
| 32 | + global $wgUser; |
| 33 | + $fl = FavoritedItem::fromUserTitle( $wgUser, $title ); |
| 34 | + $fl->removeFavorite(); |
| 35 | + $title->invalidateCache(); |
| 36 | + } |
| 37 | +} |
Property changes on: trunk/extensions/Favorites/FavUser.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 38 | + native |
Index: trunk/extensions/Favorites/SpecialFavoritelist.php |
— | — | @@ -0,0 +1,556 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * @file |
| 5 | + * @ingroup SpecialPage Favoritelist |
| 6 | + */ |
| 7 | + |
| 8 | +/** |
| 9 | + * Constructor |
| 10 | + * |
| 11 | + * @param $par Parameter passed to the page |
| 12 | + */ |
| 13 | + |
| 14 | +class SpecialFavoritelist extends SpecialPage { |
| 15 | + function __construct() { |
| 16 | + parent::__construct( 'Favoritelist' ); |
| 17 | + wfLoadExtensionMessages('Favoritelist'); |
| 18 | + } |
| 19 | + |
| 20 | + function execute( $par ) { |
| 21 | + global $wgRequest, $wgOut; |
| 22 | + $vwfav = new ViewFavorites(); |
| 23 | + |
| 24 | + $this->setHeaders(); |
| 25 | + $param = $wgRequest->getText('param'); |
| 26 | + |
| 27 | + $vwfav->wfSpecialFavoritelist( $par ); |
| 28 | + } |
| 29 | + |
| 30 | +} |
| 31 | + |
| 32 | +class ViewFavorites { |
| 33 | + |
| 34 | +function wfSpecialFavoritelist( $par ) { |
| 35 | + global $wgUser, $wgOut, $wgLang, $wgRequest; |
| 36 | + global $wgRCShowFavoritingUsers, $wgEnotifFavoritelist, $wgShowUpdatedMarker; |
| 37 | + |
| 38 | + // Add feed links |
| 39 | + $flToken = $wgUser->getOption( 'favoritelisttoken' ); |
| 40 | + if (!$flToken) { |
| 41 | + $flToken = sha1( mt_rand() . microtime( true ) ); |
| 42 | + $wgUser->setOption( 'favoritelisttoken', $flToken ); |
| 43 | + $wgUser->saveSettings(); |
| 44 | + } |
| 45 | + |
| 46 | + global $wgServer, $wgScriptPath, $wgFeedClasses; |
| 47 | + $apiParams = array( 'action' => 'feedfavoritelist', 'allrev' => 'allrev', |
| 48 | + 'flowner' => $wgUser->getName(), 'fltoken' => $flToken ); |
| 49 | + $feedTemplate = wfScript('api').'?'; |
| 50 | + |
| 51 | + foreach( $wgFeedClasses as $format => $class ) { |
| 52 | + $theseParams = $apiParams + array( 'feedformat' => $format ); |
| 53 | + $url = $feedTemplate . wfArrayToCGI( $theseParams ); |
| 54 | + $wgOut->addFeedLink( $format, $url ); |
| 55 | + } |
| 56 | + |
| 57 | + $skin = $wgUser->getSkin(); |
| 58 | + $specialTitle = SpecialPage::getTitleFor( 'Favoritelist' ); |
| 59 | + $wgOut->setRobotPolicy( 'noindex,nofollow' ); |
| 60 | + |
| 61 | + # Anons don't get a favoritelist |
| 62 | + if( $wgUser->isAnon() ) { |
| 63 | + $wgOut->setPageTitle( wfMsg( 'favoritenologin' ) ); |
| 64 | + $llink = $skin->linkKnown( |
| 65 | + SpecialPage::getTitleFor( 'Userlogin' ), |
| 66 | + wfMsgHtml( 'loginreqlink' ), |
| 67 | + array(), |
| 68 | + array( 'returnto' => $specialTitle->getPrefixedText() ) |
| 69 | + ); |
| 70 | + $wgOut->addHTML( wfMsgWikiHtml( 'favoritelistanontext', $llink ) ); |
| 71 | + return; |
| 72 | + } |
| 73 | + |
| 74 | + $wgOut->setPageTitle( wfMsg( 'favoritelist' ) ); |
| 75 | + |
| 76 | + $sub = wfMsgExt( 'favoritelistfor', 'parseinline', $wgUser->getName() ); |
| 77 | + $sub .= '<br />' . FavoritelistEditor::buildTools( $wgUser->getSkin() ); |
| 78 | + $wgOut->setSubtitle( $sub ); |
| 79 | + |
| 80 | + if( ( $mode = FavoritelistEditor::getMode( $wgRequest, $par ) ) !== false ) { |
| 81 | + $editor = new FavoritelistEditor(); |
| 82 | + $editor->execute( $wgUser, $wgOut, $wgRequest, $mode ); |
| 83 | + return; |
| 84 | + } |
| 85 | + |
| 86 | + |
| 87 | + $this->viewFavList($wgUser, $wgOut, $wgRequest, $mode); |
| 88 | + |
| 89 | +} |
| 90 | + |
| 91 | + |
| 92 | +private function viewFavList ($user, $output, $request, $mode) { |
| 93 | + global $wgUser, $wgOut, $wgLang, $wgRequest; |
| 94 | + $uid = $wgUser->getId(); |
| 95 | + $output->setPageTitle( wfMsg( 'favoritelist' ) ); |
| 96 | + if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) { |
| 97 | + $titles = $this->extractTitles( $request->getArray( 'titles' ) ); |
| 98 | + $this->unfavoriteTitles( $titles, $user ); |
| 99 | + $user->invalidateCache(); |
| 100 | + $output->addHTML( wfMsgExt( 'favoritelistedit-normal-done', 'parse', |
| 101 | + $GLOBALS['wgLang']->formatNum( count( $titles ) ) ) ); |
| 102 | + $this->showTitles( $titles, $output, $wgUser->getSkin() ); |
| 103 | + } |
| 104 | + $this->showNormalForm( $output, $user ); |
| 105 | + |
| 106 | + $dbr = wfGetDB( DB_SLAVE, 'favoritelist' ); |
| 107 | +// $recentchanges = $dbr->tableName( 'recentchanges' ); |
| 108 | + |
| 109 | + $favoritelistCount = $dbr->selectField( 'favoritelist', 'COUNT(*)', |
| 110 | + array( 'fl_user' => $uid ), __METHOD__ ); |
| 111 | + // Adjust for page X, talk:page X, which are both stored separately, |
| 112 | + // but treated together |
| 113 | + //$nitems = floor($favoritelistCount / 2); |
| 114 | + $nitems = $favoritelistCount; |
| 115 | + if( $nitems == 0 ) { |
| 116 | + $wgOut->addWikiMsg( 'nofavoritelist' ); |
| 117 | + return; |
| 118 | + } |
| 119 | + |
| 120 | +} |
| 121 | + |
| 122 | + /** |
| 123 | + * Check the edit token from a form submission |
| 124 | + * |
| 125 | + * @param $request WebRequest |
| 126 | + * @param $user User |
| 127 | + * @return bool |
| 128 | + */ |
| 129 | + private function checkToken( $request, $user ) { |
| 130 | + return $user->matchEditToken( $request->getVal( 'token' ), 'favoritelistedit' ); |
| 131 | + } |
| 132 | + |
| 133 | + /** |
| 134 | + * Extract a list of titles from a blob of text, returning |
| 135 | + * (prefixed) strings; unfavoritable titles are ignored |
| 136 | + * |
| 137 | + * @param $list mixed |
| 138 | + * @return array |
| 139 | + */ |
| 140 | + private function extractTitles( $list ) { |
| 141 | + $titles = array(); |
| 142 | + if( !is_array( $list ) ) { |
| 143 | + $list = explode( "\n", trim( $list ) ); |
| 144 | + if( !is_array( $list ) ) |
| 145 | + return array(); |
| 146 | + } |
| 147 | + foreach( $list as $text ) { |
| 148 | + $text = trim( $text ); |
| 149 | + if( strlen( $text ) > 0 ) { |
| 150 | + $title = Title::newFromText( $text ); |
| 151 | + //if( $title instanceof Title && $title->isFavoritable() ) |
| 152 | + $titles[] = $title->getPrefixedText(); |
| 153 | + } |
| 154 | + } |
| 155 | + return array_unique( $titles ); |
| 156 | + } |
| 157 | + |
| 158 | + /** |
| 159 | + * Print out a list of linked titles |
| 160 | + * |
| 161 | + * $titles can be an array of strings or Title objects; the former |
| 162 | + * is preferred, since Titles are very memory-heavy |
| 163 | + * |
| 164 | + * @param $titles An array of strings, or Title objects |
| 165 | + * @param $output OutputPage |
| 166 | + * @param $skin Skin |
| 167 | + */ |
| 168 | + private function showTitles( $titles, $output, $skin ) { |
| 169 | + $talk = wfMsgHtml( 'talkpagelinktext' ); |
| 170 | + // Do a batch existence check |
| 171 | + $batch = new LinkBatch(); |
| 172 | + foreach( $titles as $title ) { |
| 173 | + if( !$title instanceof Title ) |
| 174 | + $title = Title::newFromText( $title ); |
| 175 | + //if( $title instanceof Title ) { |
| 176 | + // $batch->addObj( $title ); |
| 177 | + // $batch->addObj( $title->getTalkPage() ); |
| 178 | + //} |
| 179 | + } |
| 180 | + $batch->execute(); |
| 181 | + // Print out the list |
| 182 | + $output->addHTML( "<ul>\n" ); |
| 183 | + foreach( $titles as $title ) { |
| 184 | + if( !$title instanceof Title ) |
| 185 | + $title = Title::newFromText( $title ); |
| 186 | + if( $title instanceof Title ) { |
| 187 | + $output->addHTML( "<li>" . $skin->link( $title ) |
| 188 | + //. ' (' . $skin->link( $title->getTalkPage(), $talk ) . ")</li>\n" ); |
| 189 | + . "</li>\n" ); |
| 190 | + } |
| 191 | + } |
| 192 | + $output->addHTML( "</ul>\n" ); |
| 193 | + } |
| 194 | + |
| 195 | + /** |
| 196 | + * Count the number of titles on a user's favoritelist, excluding talk pages |
| 197 | + * |
| 198 | + * @param $user User |
| 199 | + * @return int |
| 200 | + */ |
| 201 | + private function countFavoritelist( $user ) { |
| 202 | + $dbr = wfGetDB( DB_MASTER ); |
| 203 | + $res = $dbr->select( 'favoritelist', 'COUNT(*) AS count', array( 'fl_user' => $user->getId() ), __METHOD__ ); |
| 204 | + $row = $dbr->fetchObject( $res ); |
| 205 | + return ceil( $row->count ); // Paranoia |
| 206 | + } |
| 207 | + |
| 208 | + /** |
| 209 | + * Prepare a list of titles on a user's favoritelist (excluding talk pages) |
| 210 | + * and return an array of (prefixed) strings |
| 211 | + * |
| 212 | + * @param $user User |
| 213 | + * @return array |
| 214 | + */ |
| 215 | + private function getFavoritelist( $user ) { |
| 216 | + $list = array(); |
| 217 | + $dbr = wfGetDB( DB_MASTER ); |
| 218 | + $res = $dbr->select( |
| 219 | + 'favoritelist', |
| 220 | + '*', |
| 221 | + array( |
| 222 | + 'fl_user' => $user->getId(), |
| 223 | + ), |
| 224 | + __METHOD__ |
| 225 | + ); |
| 226 | + if( $res->numRows() > 0 ) { |
| 227 | + while( $row = $res->fetchObject() ) { |
| 228 | + $title = Title::makeTitleSafe( $row->fl_namespace, $row->fl_title ); |
| 229 | + if( $title instanceof Title && !$title->isTalkPage() ) |
| 230 | + $list[] = $title->getPrefixedText(); |
| 231 | + } |
| 232 | + $res->free(); |
| 233 | + } |
| 234 | + return $list; |
| 235 | + } |
| 236 | + |
| 237 | + /** |
| 238 | + * Get a list of titles on a user's favoritelist, excluding talk pages, |
| 239 | + * and return as a two-dimensional array with namespace, title and |
| 240 | + * redirect status |
| 241 | + * |
| 242 | + * @param $user User |
| 243 | + * @return array |
| 244 | + */ |
| 245 | + private function getFavoritelistInfo( $user ) { |
| 246 | + $titles = array(); |
| 247 | + $dbr = wfGetDB( DB_MASTER ); |
| 248 | + $uid = intval( $user->getId() ); |
| 249 | + list( $favoritelist, $page ) = $dbr->tableNamesN( 'favoritelist', 'page' ); |
| 250 | + $sql = "SELECT fl_namespace, fl_title, page_id, page_len, page_is_redirect |
| 251 | + FROM {$favoritelist} LEFT JOIN {$page} ON ( fl_namespace = page_namespace |
| 252 | + AND fl_title = page_title ) WHERE fl_user = {$uid}"; |
| 253 | + $res = $dbr->query( $sql, __METHOD__ ); |
| 254 | + if( $res && $dbr->numRows( $res ) > 0 ) { |
| 255 | + $cache = LinkCache::singleton(); |
| 256 | + while( $row = $dbr->fetchObject( $res ) ) { |
| 257 | + $title = Title::makeTitleSafe( $row->fl_namespace, $row->fl_title ); |
| 258 | + if( $title instanceof Title ) { |
| 259 | + // Update the link cache while we're at it |
| 260 | + if( $row->page_id ) { |
| 261 | + $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect ); |
| 262 | + } else { |
| 263 | + $cache->addBadLinkObj( $title ); |
| 264 | + } |
| 265 | + // Ignore non-talk |
| 266 | + if( !$title->isTalkPage() ) |
| 267 | + $titles[$row->fl_namespace][$row->fl_title] = $row->page_is_redirect; |
| 268 | + } |
| 269 | + } |
| 270 | + } |
| 271 | + return $titles; |
| 272 | + } |
| 273 | + |
| 274 | + /** |
| 275 | + * Show a message indicating the number of items on the user's favoritelist, |
| 276 | + * and return this count for additional checking |
| 277 | + * |
| 278 | + * @param $output OutputPage |
| 279 | + * @param $user User |
| 280 | + * @return int |
| 281 | + */ |
| 282 | + private function showItemCount( $output, $user ) { |
| 283 | + if( ( $count = $this->countFavoritelist( $user ) ) > 0 ) { |
| 284 | + //$output->addHTML( wfMsgExt( 'favoritelistedit-numitems', 'parse', |
| 285 | + // $GLOBALS['wgLang']->formatNum( $count ) ) ); |
| 286 | + } else { |
| 287 | + //$output->addHTML( wfMsgExt( 'favoritelistedit-noitems', 'parse' ) ); |
| 288 | + } |
| 289 | + return $count; |
| 290 | + } |
| 291 | + |
| 292 | + /** |
| 293 | + * Remove all titles from a user's favoritelist |
| 294 | + * |
| 295 | + * @param $user User |
| 296 | + */ |
| 297 | + private function clearFavoritelist( $user ) { |
| 298 | + $dbw = wfGetDB( DB_MASTER ); |
| 299 | + $dbw->delete( 'favoritelist', array( 'fl_user' => $user->getId() ), __METHOD__ ); |
| 300 | + } |
| 301 | + |
| 302 | + /** |
| 303 | + * Add a list of titles to a user's favoritelist |
| 304 | + * |
| 305 | + * $titles can be an array of strings or Title objects; the former |
| 306 | + * is preferred, since Titles are very memory-heavy |
| 307 | + * |
| 308 | + * @param $titles An array of strings, or Title objects |
| 309 | + * @param $user User |
| 310 | + */ |
| 311 | + private function favoriteTitles( $titles, $user ) { |
| 312 | + $dbw = wfGetDB( DB_MASTER ); |
| 313 | + $rows = array(); |
| 314 | + foreach( $titles as $title ) { |
| 315 | + if( !$title instanceof Title ) |
| 316 | + $title = Title::newFromText( $title ); |
| 317 | + if( $title instanceof Title ) { |
| 318 | + $rows[] = array( |
| 319 | + 'fl_user' => $user->getId(), |
| 320 | + 'fl_namespace' => ( $title->getNamespace() & ~1 ), |
| 321 | + 'fl_title' => $title->getDBkey(), |
| 322 | + 'fl_notificationtimestamp' => null, |
| 323 | + ); |
| 324 | + $rows[] = array( |
| 325 | + 'fl_user' => $user->getId(), |
| 326 | + 'fl_namespace' => ( $title->getNamespace() | 1 ), |
| 327 | + 'fl_title' => $title->getDBkey(), |
| 328 | + 'fl_notificationtimestamp' => null, |
| 329 | + ); |
| 330 | + } |
| 331 | + } |
| 332 | + $dbw->insert( 'favoritelist', $rows, __METHOD__, 'IGNORE' ); |
| 333 | + } |
| 334 | + |
| 335 | + /** |
| 336 | + * Remove a list of titles from a user's favoritelist |
| 337 | + * |
| 338 | + * $titles can be an array of strings or Title objects; the former |
| 339 | + * is preferred, since Titles are very memory-heavy |
| 340 | + * |
| 341 | + * @param $titles An array of strings, or Title objects |
| 342 | + * @param $user User |
| 343 | + */ |
| 344 | + private function unfavoriteTitles( $titles, $user ) { |
| 345 | + $dbw = wfGetDB( DB_MASTER ); |
| 346 | + foreach( $titles as $title ) { |
| 347 | + if( !$title instanceof Title ) |
| 348 | + $title = Title::newFromText( $title ); |
| 349 | + if( $title instanceof Title ) { |
| 350 | + $dbw->delete( |
| 351 | + 'favoritelist', |
| 352 | + array( |
| 353 | + 'fl_user' => $user->getId(), |
| 354 | + 'fl_namespace' => ( $title->getNamespace() & ~1 ), |
| 355 | + 'fl_title' => $title->getDBkey(), |
| 356 | + ), |
| 357 | + __METHOD__ |
| 358 | + ); |
| 359 | + $dbw->delete( |
| 360 | + 'favoritelist', |
| 361 | + array( |
| 362 | + 'fl_user' => $user->getId(), |
| 363 | + 'fl_namespace' => ( $title->getNamespace() | 1 ), |
| 364 | + 'fl_title' => $title->getDBkey(), |
| 365 | + ), |
| 366 | + __METHOD__ |
| 367 | + ); |
| 368 | + $article = new Article($title); |
| 369 | + wfRunHooks('UnfavoriteArticleComplete',array(&$user,&$article)); |
| 370 | + } |
| 371 | + } |
| 372 | + } |
| 373 | + |
| 374 | + /** |
| 375 | + * Show the standard favoritelist editing form |
| 376 | + * |
| 377 | + * @param $output OutputPage |
| 378 | + * @param $user User |
| 379 | + */ |
| 380 | + private function showNormalForm( $output, $user ) { |
| 381 | + global $wgUser; |
| 382 | + if( ( $count = $this->showItemCount( $output, $user ) ) > 0 ) { |
| 383 | + $self = SpecialPage::getTitleFor( 'Favoritelist' ); |
| 384 | + $form = Xml::openElement( 'form', array( 'method' => 'post', |
| 385 | + 'action' => $self->getLocalUrl( array( 'action' => 'edit' ) ) ) ); |
| 386 | + $form .= Xml::hidden( 'token', $wgUser->editToken( 'favoritelistedit' ) ); |
| 387 | + //$form .= "<fieldset>\n<legend>" . wfMsgHtml( 'favoritelistedit-normal-legend' ) . "</legend>"; |
| 388 | + //$form .= wfMsgExt( 'favoritelistedit-normal-explain', 'parse' ); |
| 389 | + $form .= $this->buildRemoveList( $user, $wgUser->getSkin() ); |
| 390 | + //$form .= '<p>' . Xml::submitButton( wfMsg( 'favoritelistedit-normal-submit' ) ) . '</p>'; |
| 391 | + $form .= '</fieldset></form>'; |
| 392 | + $output->addHTML( $form ); |
| 393 | + } |
| 394 | + } |
| 395 | + |
| 396 | + /** |
| 397 | + * Build the part of the standard favoritelist editing form with the actual |
| 398 | + * title selection checkboxes and stuff. Also generates a table of |
| 399 | + * contents if there's more than one heading. |
| 400 | + * |
| 401 | + * @param $user User |
| 402 | + * @param $skin Skin (really, Linker) |
| 403 | + */ |
| 404 | + private function buildRemoveList( $user, $skin ) { |
| 405 | + $list = ""; |
| 406 | + $toc = $skin->tocIndent(); |
| 407 | + $tocLength = 0; |
| 408 | + foreach( $this->getFavoritelistInfo( $user ) as $namespace => $pages ) { |
| 409 | + $tocLength++; |
| 410 | + $heading = htmlspecialchars( $this->getNamespaceHeading( $namespace ) ); |
| 411 | + $anchor = "editfavoritelist-ns" . $namespace; |
| 412 | + |
| 413 | + $list .= $skin->makeHeadLine( 2, ">", $anchor, $heading, "" ); |
| 414 | + $toc .= $skin->tocLine( $anchor, $heading, $tocLength, 1 ) . $skin->tocLineEnd(); |
| 415 | + |
| 416 | + $list .= "<ul>\n"; |
| 417 | + foreach( $pages as $dbkey => $redirect ) { |
| 418 | + $title = Title::makeTitleSafe( $namespace, $dbkey ); |
| 419 | + $list .= $this->buildRemoveLine( $title, $redirect, $skin ); |
| 420 | + } |
| 421 | + $list .= "</ul>\n"; |
| 422 | + } |
| 423 | + // ISSUE: omit the TOC if the total number of titles is low? |
| 424 | + if( $tocLength > 10 ) { |
| 425 | + $list = $skin->tocList( $toc ) . $list; |
| 426 | + } |
| 427 | + return $list; |
| 428 | + } |
| 429 | + |
| 430 | + /** |
| 431 | + * Get the correct "heading" for a namespace |
| 432 | + * |
| 433 | + * @param $namespace int |
| 434 | + * @return string |
| 435 | + */ |
| 436 | + private function getNamespaceHeading( $namespace ) { |
| 437 | + return $namespace == NS_MAIN |
| 438 | + ? wfMsgHtml( 'blanknamespace' ) |
| 439 | + : htmlspecialchars( $GLOBALS['wgContLang']->getFormattedNsText( $namespace ) ); |
| 440 | + } |
| 441 | + |
| 442 | + /** |
| 443 | + * Build a single list item containing a check box selecting a title |
| 444 | + * and a link to that title, with various additional bits |
| 445 | + * |
| 446 | + * @param $title Title |
| 447 | + * @param $redirect bool |
| 448 | + * @param $skin Skin |
| 449 | + * @return string |
| 450 | + */ |
| 451 | + private function buildRemoveLine( $title, $redirect, $skin ) { |
| 452 | + global $wgLang; |
| 453 | + |
| 454 | + $link = $skin->link( $title ); |
| 455 | + if( $redirect ) |
| 456 | + $link = '<span class="favoritelistredir">' . $link . '</span>'; |
| 457 | + $tools[] = $skin->link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) ); |
| 458 | + if( $title->exists() ) { |
| 459 | + $tools[] = $skin->link( |
| 460 | + $title, |
| 461 | + wfMsgHtml( 'history_short' ), |
| 462 | + array(), |
| 463 | + array( 'action' => 'history' ), |
| 464 | + array( 'known', 'noclasses' ) |
| 465 | + ); |
| 466 | + } |
| 467 | + if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) { |
| 468 | + $tools[] = $skin->link( |
| 469 | + SpecialPage::getTitleFor( 'Contributions', $title->getText() ), |
| 470 | + wfMsgHtml( 'contributions' ), |
| 471 | + array(), |
| 472 | + array(), |
| 473 | + array( 'known', 'noclasses' ) |
| 474 | + ); |
| 475 | + } |
| 476 | + return "<li>" |
| 477 | + //. Xml::check( 'titles[]', false, array( 'value' => $title->getPrefixedText() ) ) |
| 478 | + . $link . " (" . $wgLang->pipeList( $tools ) . ")" . "</li>\n"; |
| 479 | + } |
| 480 | + |
| 481 | + /** |
| 482 | + * Show a form for editing the favoritelist in "raw" mode |
| 483 | + * |
| 484 | + * @param $output OutputPage |
| 485 | + * @param $user User |
| 486 | + */ |
| 487 | + public function showRawForm( $output, $user ) { |
| 488 | + global $wgUser; |
| 489 | + $this->showItemCount( $output, $user ); |
| 490 | + $self = SpecialPage::getTitleFor( 'Favoritelist' ); |
| 491 | + $form = Xml::openElement( 'form', array( 'method' => 'post', |
| 492 | + 'action' => $self->getLocalUrl( array( 'action' => 'raw' ) ) ) ); |
| 493 | + $form .= Xml::hidden( 'token', $wgUser->editToken( 'favoritelistedit' ) ); |
| 494 | + $form .= '<fieldset><legend>' . wfMsgHtml( 'favoritelistedit-raw-legend' ) . '</legend>'; |
| 495 | + $form .= wfMsgExt( 'favoritelistedit-raw-explain', 'parse' ); |
| 496 | + $form .= Xml::label( wfMsg( 'favoritelistedit-raw-titles' ), 'titles' ); |
| 497 | + $form .= "<br />\n"; |
| 498 | + $form .= Xml::openElement( 'textarea', array( 'id' => 'titles', 'name' => 'titles', |
| 499 | + 'rows' => $wgUser->getIntOption( 'rows' ), 'cols' => $wgUser->getIntOption( 'cols' ) ) ); |
| 500 | + $titles = $this->getFavoritelist( $user ); |
| 501 | + foreach( $titles as $title ) |
| 502 | + $form .= htmlspecialchars( $title ) . "\n"; |
| 503 | + $form .= '</textarea>'; |
| 504 | + $form .= '<p>' . Xml::submitButton( wfMsg( 'favoritelistedit-raw-submit' ) ) . '</p>'; |
| 505 | + $form .= '</fieldset></form>'; |
| 506 | + $output->addHTML( $form ); |
| 507 | + } |
| 508 | + |
| 509 | + /** |
| 510 | + * Determine whether we are editing the favoritelist, and if so, what |
| 511 | + * kind of editing operation |
| 512 | + * |
| 513 | + * @param $request WebRequest |
| 514 | + * @param $par mixed |
| 515 | + * @return int |
| 516 | + */ |
| 517 | + public static function getMode( $request, $par ) { |
| 518 | + $mode = strtolower( $request->getVal( 'action', $par ) ); |
| 519 | + switch( $mode ) { |
| 520 | + case 'clear': |
| 521 | + return self::EDIT_CLEAR; |
| 522 | + case 'raw': |
| 523 | + return self::EDIT_RAW; |
| 524 | + case 'edit': |
| 525 | + return self::EDIT_NORMAL; |
| 526 | + default: |
| 527 | + return false; |
| 528 | + } |
| 529 | + } |
| 530 | + |
| 531 | + /** |
| 532 | + * Build a set of links for convenient navigation |
| 533 | + * between favoritelist viewing and editing modes |
| 534 | + * |
| 535 | + * @param $skin Skin to use |
| 536 | + * @return string |
| 537 | + */ |
| 538 | + public static function buildTools( $skin ) { |
| 539 | + global $wgLang; |
| 540 | + |
| 541 | + $tools = array(); |
| 542 | + $modes = array( 'view' => false, 'edit' => 'edit', 'raw' => 'raw' ); |
| 543 | + foreach( $modes as $mode => $subpage ) { |
| 544 | + // can use messages 'favoritelisttools-view', 'favoritelisttools-edit', 'favoritelisttools-raw' |
| 545 | + $tools[] = $skin->link( |
| 546 | + SpecialPage::getTitleFor( 'Favoritelist', $subpage ), |
| 547 | + wfMsgHtml( "favoritelisttools-{$mode}" ), |
| 548 | + array(), |
| 549 | + array(), |
| 550 | + array( 'known', 'noclasses' ) |
| 551 | + ); |
| 552 | + } |
| 553 | + return $wgLang->pipeList( $tools ); |
| 554 | + } |
| 555 | +} |
| 556 | + |
| 557 | + |
Property changes on: trunk/extensions/Favorites/SpecialFavoritelist.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 558 | + native |
Index: trunk/extensions/Favorites/FavTitle.php |
— | — | @@ -0,0 +1,42 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | + |
| 5 | +class FavTitle extends Title { |
| 6 | + |
| 7 | + |
| 8 | + var $mFav = null; |
| 9 | + var $mTextform; |
| 10 | + var $mTitle; |
| 11 | + |
| 12 | + function __construct() {} |
| 13 | + |
| 14 | + /** |
| 15 | + * Is $wgUser watching this page? |
| 16 | + * @return \type{\bool} |
| 17 | + */ |
| 18 | + public function userIsFavoriting() { |
| 19 | + |
| 20 | + global $wgUser, $wgArticle; |
| 21 | + $favUser = new FavUser(); |
| 22 | + if ( is_null( $this->mFav ) ) { |
| 23 | + if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn()) { |
| 24 | + $this->mFav = false; |
| 25 | + } else { |
| 26 | + $this->mFav = $favUser->isFavorited( $wgArticle->mTitle ); |
| 27 | + } |
| 28 | + } |
| 29 | + return $this->mFav; |
| 30 | + } |
| 31 | + |
| 32 | + public function moveToFav($title, &$nt ) { |
| 33 | + # Update watchlists |
| 34 | + $oldnamespace = $title->getNamespace() & ~1; |
| 35 | + $newnamespace = $nt->getNamespace() & ~1; |
| 36 | + $oldtitle = $title->getDBkey(); |
| 37 | + $newtitle = $nt->getDBkey(); |
| 38 | + |
| 39 | + if( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) { |
| 40 | + FavoritedItem::duplicateEntries( $title, $nt ); |
| 41 | + } |
| 42 | + } |
| 43 | +} |
Property changes on: trunk/extensions/Favorites/FavTitle.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 44 | + native |
Index: trunk/extensions/Favorites/Favorites_body.php |
— | — | @@ -0,0 +1,48 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class Favorites extends QuickTemplate { |
| 5 | + |
| 6 | + var $mTitle; |
| 7 | + |
| 8 | + function execute() { |
| 9 | + |
| 10 | + } |
| 11 | + |
| 12 | + function favoritesIcon( &$sktemplate, &$links ) { |
| 13 | + |
| 14 | + global $wgUseIconFavorite, $wgRequest, $wgArticle; |
| 15 | + |
| 16 | + //$sktemplate->skin = $sktemplate->data['skin']; |
| 17 | + $action = $wgRequest->getText( 'action' ); |
| 18 | + |
| 19 | + // See if this object even exists - if the user can't read it, the object doesn't get created. |
| 20 | + if ($wgArticle) { |
| 21 | + |
| 22 | + if ( $wgUseIconFavorite ) { |
| 23 | + |
| 24 | + $class = 'icon '; |
| 25 | + $place = 'views'; |
| 26 | + } else { |
| 27 | + $class = ''; |
| 28 | + $place = 'actions'; |
| 29 | + } |
| 30 | + $favTitle = new FavTitle(); |
| 31 | + |
| 32 | + //$mode = $this->mTitle->userIsFavoriting() ? 'unfavorite' : 'favorite'; |
| 33 | + $mode = $favTitle->userIsFavoriting() ? 'unfavorite' : 'favorite'; |
| 34 | + $links[$place][$mode] = array( |
| 35 | + 'class' => $class . ( ( $action == 'favorite' || $action == 'unfavorite' ) ? ' selected' : false ), |
| 36 | + 'text' => wfMsg( $mode ), // uses 'favorite' or 'unfavorite' message |
| 37 | + 'href' => $wgArticle->mTitle->getLocalUrl( 'action=' . $mode ) |
| 38 | + ); |
| 39 | + |
| 40 | + |
| 41 | + return false; |
| 42 | + } |
| 43 | + |
| 44 | +} |
| 45 | + |
| 46 | +} |
| 47 | + |
| 48 | + |
| 49 | + |
Property changes on: trunk/extensions/Favorites/Favorites_body.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 50 | + native |
Index: trunk/extensions/Favorites/images/fav-icon-loading.gif |
Cannot display: file marked as a binary type. |
svn:mime-type = image/gif |
Property changes on: trunk/extensions/Favorites/images/fav-icon-loading.gif |
___________________________________________________________________ |
Added: svn:mime-type |
2 | 51 | + image/gif |
Index: trunk/extensions/Favorites/images/fav-icons.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: trunk/extensions/Favorites/images/fav-icons.png |
___________________________________________________________________ |
Added: svn:mime-type |
3 | 52 | + image/png |
Index: trunk/extensions/Favorites/FavArticle.php |
— | — | @@ -0,0 +1,96 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | + |
| 5 | +class FavArticle extends Article { |
| 6 | + |
| 7 | + |
| 8 | + var $mTitle; |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | + /** |
| 13 | + * User-interface handler for the "favorite" action |
| 14 | + */ |
| 15 | + public function favorite() { |
| 16 | + global $wgUser, $wgOut, $wgArticle; |
| 17 | + |
| 18 | + $this->mTitle = $wgArticle->mTitle; |
| 19 | + |
| 20 | + if ( $wgUser->isAnon() ) { |
| 21 | + $wgOut->showErrorPage( 'favoritenologin', 'favoritenologintext' ); |
| 22 | + return; |
| 23 | + } |
| 24 | + if ( wfReadOnly() ) { |
| 25 | + $wgOut->readOnlyPage(); |
| 26 | + return; |
| 27 | + } |
| 28 | + if ( $this->doFavorite() ) { |
| 29 | + $wgOut->setPagetitle( wfMsg( 'addedfavorite' ) ); |
| 30 | + $wgOut->setRobotPolicy( 'noindex,nofollow' ); |
| 31 | + $wgOut->addWikiMsg( 'addedfavoritetext', $this->mTitle->getPrefixedText() ); |
| 32 | + } |
| 33 | + $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() ); |
| 34 | + } |
| 35 | + |
| 36 | + /** |
| 37 | + * Add this page to $wgUser's favoritelist |
| 38 | + * @return bool true on successful favorite operation |
| 39 | + */ |
| 40 | + public function doFavorite() { |
| 41 | + global $wgUser; |
| 42 | + $favUser = new FavUser(); |
| 43 | + if ( $wgUser->isAnon() ) { |
| 44 | + return false; |
| 45 | + } |
| 46 | + if ( wfRunHooks( 'FavoriteArticle', array( &$wgUser, &$this ) ) ) { |
| 47 | + $favUser->addFavorite( $this->mTitle ); |
| 48 | + return wfRunHooks( 'FavoriteArticleComplete', array( &$wgUser, &$this ) ); |
| 49 | + } |
| 50 | + |
| 51 | + } |
| 52 | + |
| 53 | + /** |
| 54 | + * User interface handler for the "unfavorite" action. |
| 55 | + */ |
| 56 | + public function unfavorite($action, $wgArticle) { |
| 57 | + global $wgUser, $wgOut, $wgArticle; |
| 58 | + $this->mTitle = $wgArticle->mTitle; |
| 59 | + if ( $wgUser->isAnon() ) { |
| 60 | + $wgOut->showErrorPage( 'favoritenologin', 'favoritenologintext' ); |
| 61 | + return; |
| 62 | + } |
| 63 | + if ( wfReadOnly() ) { |
| 64 | + $wgOut->readOnlyPage(); |
| 65 | + return; |
| 66 | + } |
| 67 | + if ( $this->doUnfavorite() ) { |
| 68 | + $wgOut->setPagetitle( wfMsg( 'removedfavorite' ) ); |
| 69 | + $wgOut->setRobotPolicy( 'noindex,nofollow' ); |
| 70 | + $wgOut->addWikiMsg( 'removedfavoritetext', $wgArticle->mTitle->getPrefixedText() ); |
| 71 | + } |
| 72 | + $wgOut->returnToMain( true, $wgArticle->mTitle->getPrefixedText() ); |
| 73 | + |
| 74 | + return false; |
| 75 | + |
| 76 | + } |
| 77 | + |
| 78 | + /** |
| 79 | + * Stop favoriting a page |
| 80 | + * @return bool true on successful unfavorite |
| 81 | + */ |
| 82 | + public function doUnfavorite() { |
| 83 | + global $wgUser; |
| 84 | + |
| 85 | + $favUser = new FavUser(); |
| 86 | + if ( $wgUser->isAnon() ) { |
| 87 | + return false; |
| 88 | + } |
| 89 | + if ( wfRunHooks( 'UnfavoriteArticle', array( &$wgUser, &$this ) ) ) { |
| 90 | + $favUser->removeFavorite( $this->mTitle ); |
| 91 | + return wfRunHooks( 'UnfavoriteArticleComplete', array( &$wgUser, &$this ) ); |
| 92 | + } |
| 93 | + return false; |
| 94 | + } |
| 95 | + |
| 96 | + |
| 97 | +} |
Property changes on: trunk/extensions/Favorites/FavArticle.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 98 | + native |
Index: trunk/extensions/Favorites/favorites.i18n.php |
— | — | @@ -0,0 +1,78 @@ |
| 2 | +<?php |
| 3 | +$messages = array(); |
| 4 | +$messages['en'] = array( |
| 5 | + |
| 6 | + |
| 7 | +# Edit pages |
| 8 | +'favoritethis' => 'Favorite this page', |
| 9 | + |
| 10 | +# Preferences page |
| 11 | +'prefs-favoritelist' => 'Favorites', |
| 12 | +'prefs-favoritelist-token' => 'Favorites token:', |
| 13 | +'prefs-advancedfavoritelist' => 'Advanced options', |
| 14 | + |
| 15 | +# Favoritelist |
| 16 | +'favoritelist' => 'My favorites', |
| 17 | +'myfavoritelist' => 'My favorites', |
| 18 | +'favoritelistfor' => "(for '''$1''')", |
| 19 | +'nofavoritelist' => 'You have no items in your favorites.', |
| 20 | +'favoritelistanontext' => 'Please $1 to view or edit items in your favorites.', |
| 21 | +'favoritenologin' => 'Not logged in', |
| 22 | +'favoritenologintext' => 'You must be [[Special:UserLogin|logged in]] to modify your favorites.', |
| 23 | +'addedfavorite' => 'Added to favorites', |
| 24 | +'addedfavoritetext' => 'The page "[[:$1]]" has been added to your [[Special:Favoritelist|favorites]].', |
| 25 | +'removedfavorite' => 'Removed from favorites', |
| 26 | +'removedfavoritetext' => 'The page "[[:$1]]" has been removed from [[Special:Favoritelist|your favorites]].', |
| 27 | +'favorite' => 'Favorite', |
| 28 | +'favoritethispage' => 'Favorite this page', |
| 29 | +'unfavorite' => 'Unfavorite', |
| 30 | +'unfavoritethispage' => 'Stop favoriteing', |
| 31 | +'favoritelist-options' => 'Favorites options', |
| 32 | + |
| 33 | +# Displayed when you click the "favorite" button and it is in the process of favoriteing |
| 34 | +'favoriteing' => 'Favoriteing...', |
| 35 | +'unfavoriteing' => 'Unfavoriteing...', |
| 36 | + |
| 37 | +# Keyboard access keys for power users |
| 38 | +'accesskey-pt-favoritelist' => 'l', # do not translate or duplicate this message to other languages |
| 39 | +'accesskey-ca-favorite' => 'w', # do not translate or duplicate this message to other languages |
| 40 | +'accesskey-ca-unfavorite' => 'w', # do not translate or duplicate this message to other languages |
| 41 | +'accesskey-favorite' => 'w', # do not translate or duplicate this message to other languages |
| 42 | + |
| 43 | +# Tooltip help for the actions |
| 44 | +'tooltip-pt-favoritelist' => 'The list of pages you are monitoring for changes', |
| 45 | +'tooltip-ca-favorite' => 'Add this page to your favorites', |
| 46 | +'tooltip-ca-unfavorite' => 'Remove this page from your favorites', |
| 47 | +'tooltip-favorite' => 'Add this page to your favorites', |
| 48 | + |
| 49 | +# 'all' in various places, this might be different for inflected languages |
| 50 | +'favoritelistall2' => 'all', |
| 51 | + |
| 52 | +# Favoritelist editor |
| 53 | +'favoritelistedit-numitems' => 'Your favorites contain {{PLURAL:$1|1 title|$1 titles}}, excluding talk pages.', |
| 54 | +'favoritelistedit-noitems' => 'Your favorites contain no titles.', |
| 55 | +'favoritelistedit-normal-title' => 'Edit favorites', |
| 56 | +'favoritelistedit-normal-legend' => 'Remove titles from favorites', |
| 57 | +'favoritelistedit-normal-explain' => 'Titles in your favorites are shown below. |
| 58 | +To remove a title, check the box next to it, and click "{{int:Favoritelistedit-normal-submit}}". |
| 59 | +You can also [[Special:Favoritelist/raw|edit the raw list]].', |
| 60 | +'favoritelistedit-normal-submit' => 'Remove titles', |
| 61 | +'favoritelistedit-normal-done' => '{{PLURAL:$1|1 title was|$1 titles were}} removed from your favorites:', |
| 62 | +'favoritelistedit-raw-title' => 'Edit raw favorites list', |
| 63 | +'favoritelistedit-raw-legend' => 'Edit raw favorites list', |
| 64 | +'favoritelistedit-raw-explain' => 'Titles in your favorites are shown below, and can be edited by adding to and removing from the list; |
| 65 | +one title per line. |
| 66 | +When finished, click "{{int:Favoritelistedit-raw-submit}}". |
| 67 | +You can also [[Special:Favoritelist/edit|use the standard editor]].', |
| 68 | +'favoritelistedit-raw-titles' => 'Titles:', |
| 69 | +'favoritelistedit-raw-submit' => 'Update favorites', |
| 70 | +'favoritelistedit-raw-done' => 'Your favorites have been updated.', |
| 71 | +'favoritelistedit-raw-added' => '{{PLURAL:$1|1 title was|$1 titles were}} added:', |
| 72 | +'favoritelistedit-raw-removed' => '{{PLURAL:$1|1 title was|$1 titles were}} removed:', |
| 73 | + |
| 74 | +# Favoritelist editing tools |
| 75 | +'favoritelisttools-view' => 'View Favorites', |
| 76 | +'favoritelisttools-edit' => 'View and edit favorites', |
| 77 | +'favoritelisttools-raw' => 'Edit raw favorites', |
| 78 | + |
| 79 | +); |
\ No newline at end of file |
Property changes on: trunk/extensions/Favorites/favorites.i18n.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 80 | + native |
Index: trunk/extensions/Favorites/FavoritedItem.php |
— | — | @@ -0,0 +1,131 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * @file |
| 5 | + * @ingroup Favoritelist |
| 6 | + */ |
| 7 | + |
| 8 | +/** |
| 9 | + * @ingroup Favoritelist |
| 10 | + */ |
| 11 | +class FavoritedItem { |
| 12 | + var $mTitle, $mUser, $id, $ns, $ti; |
| 13 | + |
| 14 | + /** |
| 15 | + * Create a FavoritedItem object with the given user and title |
| 16 | + * @param $user User: the user to use for (un)favoriting |
| 17 | + * @param $title Title: the title we're going to (un)favorite |
| 18 | + * @return FavoritedItem object |
| 19 | + */ |
| 20 | + public static function fromUserTitle( $user, $title ) { |
| 21 | + $fl = new FavoritedItem; |
| 22 | + $fl->mUser = $user; |
| 23 | + $fl->mTitle = $title; |
| 24 | + $fl->id = $user->getId(); |
| 25 | + $fl->ns = $title->getNamespace(); |
| 26 | + $fl->ti = $title->getDBkey(); |
| 27 | + return $fl; |
| 28 | + } |
| 29 | + |
| 30 | + /** |
| 31 | + * Is mTitle being favorited by mUser? |
| 32 | + * @return bool |
| 33 | + */ |
| 34 | + public function isFavorited() { |
| 35 | + |
| 36 | + $dbr = wfGetDB( DB_SLAVE ); |
| 37 | + $res = $dbr->select( 'favoritelist', 1, array( 'fl_user' => $this->id, 'fl_namespace' => $this->ns, |
| 38 | + 'fl_title' => $this->ti ), __METHOD__ ); |
| 39 | + $isfavorited = ($dbr->numRows( $res ) > 0) ? 1 : 0; |
| 40 | + return $isfavorited; |
| 41 | + } |
| 42 | + |
| 43 | + /** |
| 44 | + * Given a title and user (assumes the object is setup), add the favorite to the |
| 45 | + * database. |
| 46 | + * @return bool (always true) |
| 47 | + */ |
| 48 | + public function addFavorite() { |
| 49 | + wfProfileIn( __METHOD__ ); |
| 50 | + |
| 51 | + $dbw = wfGetDB( DB_MASTER ); |
| 52 | + $dbw->insert( 'favoritelist', |
| 53 | + array( |
| 54 | + 'fl_user' => $this->id, |
| 55 | + 'fl_namespace' => MWNamespace::getSubject($this->ns), |
| 56 | + 'fl_title' => $this->ti, |
| 57 | + 'fl_notificationtimestamp' => null |
| 58 | + ), __METHOD__, 'IGNORE' ); |
| 59 | + |
| 60 | + wfProfileOut( __METHOD__ ); |
| 61 | + return true; |
| 62 | + } |
| 63 | + |
| 64 | + /** |
| 65 | + * Same as addFavorite, only the opposite. |
| 66 | + * @return bool |
| 67 | + */ |
| 68 | + public function removeFavorite() { |
| 69 | + $success = false; |
| 70 | + $dbw = wfGetDB( DB_MASTER ); |
| 71 | + $dbw->delete( 'favoritelist', |
| 72 | + array( |
| 73 | + 'fl_user' => $this->id, |
| 74 | + 'fl_namespace' => MWNamespace::getSubject($this->ns), |
| 75 | + 'fl_title' => $this->ti |
| 76 | + ), __METHOD__ |
| 77 | + ); |
| 78 | + if ( $dbw->affectedRows() ) { |
| 79 | + $success = true; |
| 80 | + } |
| 81 | + return $success; |
| 82 | + } |
| 83 | + |
| 84 | + /** |
| 85 | + * Check if the given title already is favorited by the user, and if so |
| 86 | + * add favorite on a new title. To be used for page renames and such. |
| 87 | + * |
| 88 | + * @param $ot Title: page title to duplicate entries from, if present |
| 89 | + * @param $nt Title: page title to add favorite on |
| 90 | + */ |
| 91 | + public static function duplicateEntries( $ot, $nt ) { |
| 92 | + FavoritedItem::doDuplicateEntries( $ot->getSubjectPage(), $nt->getSubjectPage() ); |
| 93 | + FavoritedItem::doDuplicateEntries( $ot->getTalkPage(), $nt->getTalkPage() ); |
| 94 | + } |
| 95 | + |
| 96 | + /** |
| 97 | + * Handle duplicate entries. Backend for duplicateEntries(). |
| 98 | + */ |
| 99 | + private static function doDuplicateEntries( $ot, $nt ) { |
| 100 | + $oldnamespace = $ot->getNamespace(); |
| 101 | + $newnamespace = $nt->getNamespace(); |
| 102 | + $oldtitle = $ot->getDBkey(); |
| 103 | + $newtitle = $nt->getDBkey(); |
| 104 | + |
| 105 | + $dbw = wfGetDB( DB_MASTER ); |
| 106 | + $res = $dbw->select( 'favoritelist', 'fl_user', |
| 107 | + array( 'fl_namespace' => $oldnamespace, 'fl_title' => $oldtitle ), |
| 108 | + __METHOD__, 'FOR UPDATE' |
| 109 | + ); |
| 110 | + # Construct array to replace into the favoritelist |
| 111 | + $values = array(); |
| 112 | + while ( $s = $dbw->fetchObject( $res ) ) { |
| 113 | + $values[] = array( |
| 114 | + 'fl_user' => $s->fl_user, |
| 115 | + 'fl_namespace' => $newnamespace, |
| 116 | + 'fl_title' => $newtitle |
| 117 | + ); |
| 118 | + } |
| 119 | + $dbw->freeResult( $res ); |
| 120 | + |
| 121 | + if( empty( $values ) ) { |
| 122 | + // Nothing to do |
| 123 | + return true; |
| 124 | + } |
| 125 | + |
| 126 | + # Perform replace |
| 127 | + # Note that multi-row replace is very efficient for MySQL but may be inefficient for |
| 128 | + # some other DBMSes, mostly due to poor simulation by us |
| 129 | + $dbw->replace( 'favoritelist', array( array( 'fl_user', 'fl_namespace', 'fl_title' ) ), $values, __METHOD__ ); |
| 130 | + return true; |
| 131 | + } |
| 132 | +} |
Property changes on: trunk/extensions/Favorites/FavoritedItem.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 133 | + native |
Index: trunk/extensions/Favorites/FavoritelistEditor.php |
— | — | @@ -0,0 +1,512 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Provides the UI through which users can perform editing |
| 6 | + * operations on their favoritelist |
| 7 | + * |
| 8 | + * @ingroup favoritelist |
| 9 | + */ |
| 10 | +class FavoritelistEditor { |
| 11 | + |
| 12 | + /** |
| 13 | + * Editing modes |
| 14 | + */ |
| 15 | + const EDIT_CLEAR = 1; |
| 16 | + const EDIT_RAW = 2; |
| 17 | + const EDIT_NORMAL = 3; |
| 18 | + |
| 19 | + /** |
| 20 | + * Main execution point |
| 21 | + * |
| 22 | + * @param $user User |
| 23 | + * @param $output OutputPage |
| 24 | + * @param $request WebRequest |
| 25 | + * @param $mode int |
| 26 | + */ |
| 27 | + public function execute( $user, $output, $request, $mode ) { |
| 28 | + global $wgUser; |
| 29 | + if( wfReadOnly() ) { |
| 30 | + $output->readOnlyPage(); |
| 31 | + return; |
| 32 | + } |
| 33 | + switch( $mode ) { |
| 34 | + case self::EDIT_CLEAR: |
| 35 | + // The "Clear" link scared people too much. |
| 36 | + // Pass on to the raw editor, from which it's very easy to clear. |
| 37 | + case self::EDIT_RAW: |
| 38 | + $output->setPageTitle( wfMsg( 'favoritelistedit-raw-title' ) ); |
| 39 | + if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) { |
| 40 | + $wanted = $this->extractTitles( $request->getText( 'titles' ) ); |
| 41 | + $current = $this->getFavoritelist( $user ); |
| 42 | + if( count( $wanted ) > 0 ) { |
| 43 | + $toFavorite = array_diff( $wanted, $current ); |
| 44 | + $toUnfavorite = array_diff( $current, $wanted ); |
| 45 | + $this->favoriteTitles( $toFavorite, $user ); |
| 46 | + $this->unfavoriteTitles( $toUnfavorite, $user ); |
| 47 | + $user->invalidateCache(); |
| 48 | + if( count( $toFavorite ) > 0 || count( $toUnfavorite ) > 0 ) |
| 49 | + $output->addHTML( wfMsgExt( 'favoritelistedit-raw-done', 'parse' ) ); |
| 50 | + if( ( $count = count( $toFavorite ) ) > 0 ) { |
| 51 | + $output->addHTML( wfMsgExt( 'favoritelistedit-raw-added', 'parse', $count ) ); |
| 52 | + $this->showTitles( $toFavorite, $output, $wgUser->getSkin() ); |
| 53 | + } |
| 54 | + if( ( $count = count( $toUnfavorite ) ) > 0 ) { |
| 55 | + $output->addHTML( wfMsgExt( 'favoritelistedit-raw-removed', 'parse', $count ) ); |
| 56 | + $this->showTitles( $toUnfavorite, $output, $wgUser->getSkin() ); |
| 57 | + } |
| 58 | + } else { |
| 59 | + $this->clearFavoritelist( $user ); |
| 60 | + $user->invalidateCache(); |
| 61 | + $output->addHTML( wfMsgExt( 'favoritelistedit-raw-removed', 'parse', count( $current ) ) ); |
| 62 | + $this->showTitles( $current, $output, $wgUser->getSkin() ); |
| 63 | + } |
| 64 | + } |
| 65 | + $this->showRawForm( $output, $user ); |
| 66 | + break; |
| 67 | + case self::EDIT_NORMAL: |
| 68 | + $output->setPageTitle( wfMsg( 'favoritelistedit-normal-title' ) ); |
| 69 | + if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) { |
| 70 | + $titles = $this->extractTitles( $request->getArray( 'titles' ) ); |
| 71 | + $this->unfavoriteTitles( $titles, $user ); |
| 72 | + $user->invalidateCache(); |
| 73 | + $output->addHTML( wfMsgExt( 'favoritelistedit-normal-done', 'parse', |
| 74 | + $GLOBALS['wgLang']->formatNum( count( $titles ) ) ) ); |
| 75 | + $this->showTitles( $titles, $output, $wgUser->getSkin() ); |
| 76 | + } |
| 77 | + $this->showNormalForm( $output, $user ); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + /** |
| 82 | + * Check the edit token from a form submission |
| 83 | + * |
| 84 | + * @param $request WebRequest |
| 85 | + * @param $user User |
| 86 | + * @return bool |
| 87 | + */ |
| 88 | + private function checkToken( $request, $user ) { |
| 89 | + return $user->matchEditToken( $request->getVal( 'token' ), 'favoritelistedit' ); |
| 90 | + } |
| 91 | + |
| 92 | + /** |
| 93 | + * Extract a list of titles from a blob of text, returning |
| 94 | + * (prefixed) strings; unfavoritable titles are ignored |
| 95 | + * |
| 96 | + * @param $list mixed |
| 97 | + * @return array |
| 98 | + */ |
| 99 | + private function extractTitles( $list ) { |
| 100 | + $titles = array(); |
| 101 | + if( !is_array( $list ) ) { |
| 102 | + $list = explode( "\n", trim( $list ) ); |
| 103 | + if( !is_array( $list ) ) |
| 104 | + return array(); |
| 105 | + } |
| 106 | + foreach( $list as $text ) { |
| 107 | + $text = trim( $text ); |
| 108 | + if( strlen( $text ) > 0 ) { |
| 109 | + $title = Title::newFromText( $text ); |
| 110 | + //if( $title instanceof Title && $title->isFavoritable() ) |
| 111 | + $titles[] = $title->getPrefixedText(); |
| 112 | + } |
| 113 | + } |
| 114 | + return array_unique( $titles ); |
| 115 | + } |
| 116 | + |
| 117 | + /** |
| 118 | + * Print out a list of linked titles |
| 119 | + * |
| 120 | + * $titles can be an array of strings or Title objects; the former |
| 121 | + * is preferred, since Titles are very memory-heavy |
| 122 | + * |
| 123 | + * @param $titles An array of strings, or Title objects |
| 124 | + * @param $output OutputPage |
| 125 | + * @param $skin Skin |
| 126 | + */ |
| 127 | + private function showTitles( $titles, $output, $skin ) { |
| 128 | + $talk = wfMsgHtml( 'talkpagelinktext' ); |
| 129 | + // Do a batch existence check |
| 130 | + $batch = new LinkBatch(); |
| 131 | + foreach( $titles as $title ) { |
| 132 | + if( !$title instanceof Title ) |
| 133 | + $title = Title::newFromText( $title ); |
| 134 | + if( $title instanceof Title ) { |
| 135 | + $batch->addObj( $title ); |
| 136 | + $batch->addObj( $title->getTalkPage() ); |
| 137 | + } |
| 138 | + } |
| 139 | + $batch->execute(); |
| 140 | + // Print out the list |
| 141 | + $output->addHTML( "<ul>\n" ); |
| 142 | + foreach( $titles as $title ) { |
| 143 | + if( !$title instanceof Title ) |
| 144 | + $title = Title::newFromText( $title ); |
| 145 | + if( $title instanceof Title ) { |
| 146 | + $output->addHTML( "<li>" . $skin->link( $title ) |
| 147 | + . ' (' . $skin->link( $title->getTalkPage(), $talk ) . ")</li>\n" ); |
| 148 | + } |
| 149 | + } |
| 150 | + $output->addHTML( "</ul>\n" ); |
| 151 | + } |
| 152 | + |
| 153 | + /** |
| 154 | + * Count the number of titles on a user's favoritelist, excluding talk pages |
| 155 | + * |
| 156 | + * @param $user User |
| 157 | + * @return int |
| 158 | + */ |
| 159 | + private function countFavoritelist( $user ) { |
| 160 | + $dbr = wfGetDB( DB_MASTER ); |
| 161 | + $res = $dbr->select( 'favoritelist', 'COUNT(*) AS count', array( 'fl_user' => $user->getId() ), __METHOD__ ); |
| 162 | + $row = $dbr->fetchObject( $res ); |
| 163 | + return ceil( $row->count / 2 ); // Paranoia |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * Prepare a list of titles on a user's favoritelist (excluding talk pages) |
| 168 | + * and return an array of (prefixed) strings |
| 169 | + * |
| 170 | + * @param $user User |
| 171 | + * @return array |
| 172 | + */ |
| 173 | + private function getFavoritelist( $user ) { |
| 174 | + $list = array(); |
| 175 | + $dbr = wfGetDB( DB_MASTER ); |
| 176 | + $res = $dbr->select( |
| 177 | + 'favoritelist', |
| 178 | + '*', |
| 179 | + array( |
| 180 | + 'fl_user' => $user->getId(), |
| 181 | + ), |
| 182 | + __METHOD__ |
| 183 | + ); |
| 184 | + if( $res->numRows() > 0 ) { |
| 185 | + while( $row = $res->fetchObject() ) { |
| 186 | + $title = Title::makeTitleSafe( $row->fl_namespace, $row->fl_title ); |
| 187 | + if( $title instanceof Title && !$title->isTalkPage() ) |
| 188 | + $list[] = $title->getPrefixedText(); |
| 189 | + } |
| 190 | + $res->free(); |
| 191 | + } |
| 192 | + return $list; |
| 193 | + } |
| 194 | + |
| 195 | + /** |
| 196 | + * Get a list of titles on a user's favoritelist, excluding talk pages, |
| 197 | + * and return as a two-dimensional array with namespace, title and |
| 198 | + * redirect status |
| 199 | + * |
| 200 | + * @param $user User |
| 201 | + * @return array |
| 202 | + */ |
| 203 | + private function getFavoritelistInfo( $user ) { |
| 204 | + $titles = array(); |
| 205 | + $dbr = wfGetDB( DB_MASTER ); |
| 206 | + $uid = intval( $user->getId() ); |
| 207 | + list( $favoritelist, $page ) = $dbr->tableNamesN( 'favoritelist', 'page' ); |
| 208 | + $sql = "SELECT fl_namespace, fl_title, page_id, page_len, page_is_redirect |
| 209 | + FROM {$favoritelist} LEFT JOIN {$page} ON ( fl_namespace = page_namespace |
| 210 | + AND fl_title = page_title ) WHERE fl_user = {$uid}"; |
| 211 | + $res = $dbr->query( $sql, __METHOD__ ); |
| 212 | + if( $res && $dbr->numRows( $res ) > 0 ) { |
| 213 | + $cache = LinkCache::singleton(); |
| 214 | + while( $row = $dbr->fetchObject( $res ) ) { |
| 215 | + $title = Title::makeTitleSafe( $row->fl_namespace, $row->fl_title ); |
| 216 | + if( $title instanceof Title ) { |
| 217 | + // Update the link cache while we're at it |
| 218 | + if( $row->page_id ) { |
| 219 | + $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect ); |
| 220 | + } else { |
| 221 | + $cache->addBadLinkObj( $title ); |
| 222 | + } |
| 223 | + // Ignore non-talk |
| 224 | + if( !$title->isTalkPage() ) |
| 225 | + $titles[$row->fl_namespace][$row->fl_title] = $row->page_is_redirect; |
| 226 | + } |
| 227 | + } |
| 228 | + } |
| 229 | + return $titles; |
| 230 | + } |
| 231 | + |
| 232 | + /** |
| 233 | + * Show a message indicating the number of items on the user's favoritelist, |
| 234 | + * and return this count for additional checking |
| 235 | + * |
| 236 | + * @param $output OutputPage |
| 237 | + * @param $user User |
| 238 | + * @return int |
| 239 | + */ |
| 240 | + private function showItemCount( $output, $user ) { |
| 241 | + if( ( $count = $this->countFavoritelist( $user ) ) > 0 ) { |
| 242 | + $output->addHTML( wfMsgExt( 'favoritelistedit-numitems', 'parse', |
| 243 | + $GLOBALS['wgLang']->formatNum( $count ) ) ); |
| 244 | + } else { |
| 245 | + $output->addHTML( wfMsgExt( 'favoritelistedit-noitems', 'parse' ) ); |
| 246 | + } |
| 247 | + return $count; |
| 248 | + } |
| 249 | + |
| 250 | + /** |
| 251 | + * Remove all titles from a user's favoritelist |
| 252 | + * |
| 253 | + * @param $user User |
| 254 | + */ |
| 255 | + private function clearFavoritelist( $user ) { |
| 256 | + $dbw = wfGetDB( DB_MASTER ); |
| 257 | + $dbw->delete( 'favoritelist', array( 'fl_user' => $user->getId() ), __METHOD__ ); |
| 258 | + } |
| 259 | + |
| 260 | + /** |
| 261 | + * Add a list of titles to a user's favoritelist |
| 262 | + * |
| 263 | + * $titles can be an array of strings or Title objects; the former |
| 264 | + * is preferred, since Titles are very memory-heavy |
| 265 | + * |
| 266 | + * @param $titles An array of strings, or Title objects |
| 267 | + * @param $user User |
| 268 | + */ |
| 269 | + private function favoriteTitles( $titles, $user ) { |
| 270 | + $dbw = wfGetDB( DB_MASTER ); |
| 271 | + $rows = array(); |
| 272 | + foreach( $titles as $title ) { |
| 273 | + if( !$title instanceof Title ) |
| 274 | + $title = Title::newFromText( $title ); |
| 275 | + if( $title instanceof Title ) { |
| 276 | + $rows[] = array( |
| 277 | + 'fl_user' => $user->getId(), |
| 278 | + 'fl_namespace' => ( $title->getNamespace() & ~1 ), |
| 279 | + 'fl_title' => $title->getDBkey(), |
| 280 | + 'fl_notificationtimestamp' => null, |
| 281 | + ); |
| 282 | + $rows[] = array( |
| 283 | + 'fl_user' => $user->getId(), |
| 284 | + 'fl_namespace' => ( $title->getNamespace() | 1 ), |
| 285 | + 'fl_title' => $title->getDBkey(), |
| 286 | + 'fl_notificationtimestamp' => null, |
| 287 | + ); |
| 288 | + } |
| 289 | + } |
| 290 | + $dbw->insert( 'favoritelist', $rows, __METHOD__, 'IGNORE' ); |
| 291 | + } |
| 292 | + |
| 293 | + /** |
| 294 | + * Remove a list of titles from a user's favoritelist |
| 295 | + * |
| 296 | + * $titles can be an array of strings or Title objects; the former |
| 297 | + * is preferred, since Titles are very memory-heavy |
| 298 | + * |
| 299 | + * @param $titles An array of strings, or Title objects |
| 300 | + * @param $user User |
| 301 | + */ |
| 302 | + private function unfavoriteTitles( $titles, $user ) { |
| 303 | + $dbw = wfGetDB( DB_MASTER ); |
| 304 | + foreach( $titles as $title ) { |
| 305 | + if( !$title instanceof Title ) |
| 306 | + $title = Title::newFromText( $title ); |
| 307 | + if( $title instanceof Title ) { |
| 308 | + $dbw->delete( |
| 309 | + 'favoritelist', |
| 310 | + array( |
| 311 | + 'fl_user' => $user->getId(), |
| 312 | + 'fl_namespace' => ( $title->getNamespace() & ~1 ), |
| 313 | + 'fl_title' => $title->getDBkey(), |
| 314 | + ), |
| 315 | + __METHOD__ |
| 316 | + ); |
| 317 | + $dbw->delete( |
| 318 | + 'favoritelist', |
| 319 | + array( |
| 320 | + 'fl_user' => $user->getId(), |
| 321 | + 'fl_namespace' => ( $title->getNamespace() | 1 ), |
| 322 | + 'fl_title' => $title->getDBkey(), |
| 323 | + ), |
| 324 | + __METHOD__ |
| 325 | + ); |
| 326 | + $article = new Article($title); |
| 327 | + wfRunHooks('UnfavoriteArticleComplete',array(&$user,&$article)); |
| 328 | + } |
| 329 | + } |
| 330 | + } |
| 331 | + |
| 332 | + /** |
| 333 | + * Show the standard favoritelist editing form |
| 334 | + * |
| 335 | + * @param $output OutputPage |
| 336 | + * @param $user User |
| 337 | + */ |
| 338 | + private function showNormalForm( $output, $user ) { |
| 339 | + global $wgUser; |
| 340 | + if( ( $count = $this->showItemCount( $output, $user ) ) > 0 ) { |
| 341 | + $self = SpecialPage::getTitleFor( 'Favoritelist' ); |
| 342 | + $form = Xml::openElement( 'form', array( 'method' => 'post', |
| 343 | + 'action' => $self->getLocalUrl( array( 'action' => 'edit' ) ) ) ); |
| 344 | + $form .= Xml::hidden( 'token', $wgUser->editToken( 'favoritelistedit' ) ); |
| 345 | + $form .= "<fieldset>\n<legend>" . wfMsgHtml( 'favoritelistedit-normal-legend' ) . "</legend>"; |
| 346 | + $form .= wfMsgExt( 'favoritelistedit-normal-explain', 'parse' ); |
| 347 | + $form .= $this->buildRemoveList( $user, $wgUser->getSkin() ); |
| 348 | + $form .= '<p>' . Xml::submitButton( wfMsg( 'favoritelistedit-normal-submit' ) ) . '</p>'; |
| 349 | + $form .= '</fieldset></form>'; |
| 350 | + $output->addHTML( $form ); |
| 351 | + } |
| 352 | + } |
| 353 | + |
| 354 | + /** |
| 355 | + * Build the part of the standard favoritelist editing form with the actual |
| 356 | + * title selection checkboxes and stuff. Also generates a table of |
| 357 | + * contents if there's more than one heading. |
| 358 | + * |
| 359 | + * @param $user User |
| 360 | + * @param $skin Skin (really, Linker) |
| 361 | + */ |
| 362 | + private function buildRemoveList( $user, $skin ) { |
| 363 | + $list = ""; |
| 364 | + $toc = $skin->tocIndent(); |
| 365 | + $tocLength = 0; |
| 366 | + foreach( $this->getFavoritelistInfo( $user ) as $namespace => $pages ) { |
| 367 | + $tocLength++; |
| 368 | + $heading = htmlspecialchars( $this->getNamespaceHeading( $namespace ) ); |
| 369 | + $anchor = "editfavoritelist-ns" . $namespace; |
| 370 | + |
| 371 | + $list .= $skin->makeHeadLine( 2, ">", $anchor, $heading, "" ); |
| 372 | + $toc .= $skin->tocLine( $anchor, $heading, $tocLength, 1 ) . $skin->tocLineEnd(); |
| 373 | + |
| 374 | + $list .= "<ul>\n"; |
| 375 | + foreach( $pages as $dbkey => $redirect ) { |
| 376 | + $title = Title::makeTitleSafe( $namespace, $dbkey ); |
| 377 | + $list .= $this->buildRemoveLine( $title, $redirect, $skin ); |
| 378 | + } |
| 379 | + $list .= "</ul>\n"; |
| 380 | + } |
| 381 | + // ISSUE: omit the TOC if the total number of titles is low? |
| 382 | + if( $tocLength > 1 ) { |
| 383 | + $list = $skin->tocList( $toc ) . $list; |
| 384 | + } |
| 385 | + return $list; |
| 386 | + } |
| 387 | + |
| 388 | + /** |
| 389 | + * Get the correct "heading" for a namespace |
| 390 | + * |
| 391 | + * @param $namespace int |
| 392 | + * @return string |
| 393 | + */ |
| 394 | + private function getNamespaceHeading( $namespace ) { |
| 395 | + return $namespace == NS_MAIN |
| 396 | + ? wfMsgHtml( 'blanknamespace' ) |
| 397 | + : htmlspecialchars( $GLOBALS['wgContLang']->getFormattedNsText( $namespace ) ); |
| 398 | + } |
| 399 | + |
| 400 | + /** |
| 401 | + * Build a single list item containing a check box selecting a title |
| 402 | + * and a link to that title, with various additional bits |
| 403 | + * |
| 404 | + * @param $title Title |
| 405 | + * @param $redirect bool |
| 406 | + * @param $skin Skin |
| 407 | + * @return string |
| 408 | + */ |
| 409 | + private function buildRemoveLine( $title, $redirect, $skin ) { |
| 410 | + global $wgLang; |
| 411 | + |
| 412 | + $link = $skin->link( $title ); |
| 413 | + if( $redirect ) |
| 414 | + $link = '<span class="favoritelistredir">' . $link . '</span>'; |
| 415 | + $tools[] = $skin->link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) ); |
| 416 | + if( $title->exists() ) { |
| 417 | + $tools[] = $skin->link( |
| 418 | + $title, |
| 419 | + wfMsgHtml( 'history_short' ), |
| 420 | + array(), |
| 421 | + array( 'action' => 'history' ), |
| 422 | + array( 'known', 'noclasses' ) |
| 423 | + ); |
| 424 | + } |
| 425 | + if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) { |
| 426 | + $tools[] = $skin->link( |
| 427 | + SpecialPage::getTitleFor( 'Contributions', $title->getText() ), |
| 428 | + wfMsgHtml( 'contributions' ), |
| 429 | + array(), |
| 430 | + array(), |
| 431 | + array( 'known', 'noclasses' ) |
| 432 | + ); |
| 433 | + } |
| 434 | + return "<li>" |
| 435 | + . Xml::check( 'titles[]', false, array( 'value' => $title->getPrefixedText() ) ) |
| 436 | + . $link . " (" . $wgLang->pipeList( $tools ) . ")" . "</li>\n"; |
| 437 | + } |
| 438 | + |
| 439 | + /** |
| 440 | + * Show a form for editing the favoritelist in "raw" mode |
| 441 | + * |
| 442 | + * @param $output OutputPage |
| 443 | + * @param $user User |
| 444 | + */ |
| 445 | + public function showRawForm( $output, $user ) { |
| 446 | + global $wgUser; |
| 447 | + $this->showItemCount( $output, $user ); |
| 448 | + $self = SpecialPage::getTitleFor( 'Favoritelist' ); |
| 449 | + $form = Xml::openElement( 'form', array( 'method' => 'post', |
| 450 | + 'action' => $self->getLocalUrl( array( 'action' => 'raw' ) ) ) ); |
| 451 | + $form .= Xml::hidden( 'token', $wgUser->editToken( 'favoritelistedit' ) ); |
| 452 | + $form .= '<fieldset><legend>' . wfMsgHtml( 'favoritelistedit-raw-legend' ) . '</legend>'; |
| 453 | + $form .= wfMsgExt( 'favoritelistedit-raw-explain', 'parse' ); |
| 454 | + $form .= Xml::label( wfMsg( 'favoritelistedit-raw-titles' ), 'titles' ); |
| 455 | + $form .= "<br />\n"; |
| 456 | + $form .= Xml::openElement( 'textarea', array( 'id' => 'titles', 'name' => 'titles', |
| 457 | + 'rows' => $wgUser->getIntOption( 'rows' ), 'cols' => $wgUser->getIntOption( 'cols' ) ) ); |
| 458 | + $titles = $this->getFavoritelist( $user ); |
| 459 | + foreach( $titles as $title ) |
| 460 | + $form .= htmlspecialchars( $title ) . "\n"; |
| 461 | + $form .= '</textarea>'; |
| 462 | + $form .= '<p>' . Xml::submitButton( wfMsg( 'favoritelistedit-raw-submit' ) ) . '</p>'; |
| 463 | + $form .= '</fieldset></form>'; |
| 464 | + $output->addHTML( $form ); |
| 465 | + } |
| 466 | + |
| 467 | + /** |
| 468 | + * Determine whether we are editing the favoritelist, and if so, what |
| 469 | + * kind of editing operation |
| 470 | + * |
| 471 | + * @param $request WebRequest |
| 472 | + * @param $par mixed |
| 473 | + * @return int |
| 474 | + */ |
| 475 | + public static function getMode( $request, $par ) { |
| 476 | + $mode = strtolower( $request->getVal( 'action', $par ) ); |
| 477 | + switch( $mode ) { |
| 478 | + case 'clear': |
| 479 | + return self::EDIT_CLEAR; |
| 480 | + case 'raw': |
| 481 | + return self::EDIT_RAW; |
| 482 | + case 'edit': |
| 483 | + return self::EDIT_NORMAL; |
| 484 | + default: |
| 485 | + return false; |
| 486 | + } |
| 487 | + } |
| 488 | + |
| 489 | + /** |
| 490 | + * Build a set of links for convenient navigation |
| 491 | + * between favoritelist viewing and editing modes |
| 492 | + * |
| 493 | + * @param $skin Skin to use |
| 494 | + * @return string |
| 495 | + */ |
| 496 | + public static function buildTools( $skin ) { |
| 497 | + global $wgLang; |
| 498 | + |
| 499 | + $tools = array(); |
| 500 | + $modes = array( 'view' => false, 'edit' => 'edit', 'raw' => 'raw' ); |
| 501 | + foreach( $modes as $mode => $subpage ) { |
| 502 | + // can use messages 'favoritelisttools-view', 'favoritelisttools-edit', 'favoritelisttools-raw' |
| 503 | + $tools[] = $skin->link( |
| 504 | + SpecialPage::getTitleFor( 'Favoritelist', $subpage ), |
| 505 | + wfMsgHtml( "favoritelisttools-{$mode}" ), |
| 506 | + array(), |
| 507 | + array(), |
| 508 | + array( 'known', 'noclasses' ) |
| 509 | + ); |
| 510 | + } |
| 511 | + return $wgLang->pipeList( $tools ); |
| 512 | + } |
| 513 | +} |
Property changes on: trunk/extensions/Favorites/FavoritelistEditor.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 514 | + native |
Index: trunk/extensions/Favorites/Favorites.php |
— | — | @@ -0,0 +1,140 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | +Copyright (C) 2011 Jeremy Lemley |
| 5 | + |
| 6 | + This program is free software: you can redistribute it and/or modify |
| 7 | + it under the terms of the GNU General Public License as published by |
| 8 | + the Free Software Foundation, either version 3 of the License, or |
| 9 | + (at your option) any later version. |
| 10 | + |
| 11 | + This program is distributed in the hope that it will be useful, |
| 12 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | + GNU General Public License for more details. |
| 15 | + |
| 16 | + You should have received a copy of the GNU General Public License |
| 17 | + along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 18 | + |
| 19 | +*/ |
| 20 | + |
| 21 | +$wgExtensionCredits['specialpage'][] = array( |
| 22 | + 'path' => __FILE__, |
| 23 | + 'name' => 'Favorites', |
| 24 | + 'author' => 'Jeremy Lemley', |
| 25 | + 'description' => 'A method of creating a favorites list', |
| 26 | + 'version' => '0.0.2', |
| 27 | + 'url' => "http://www.mediawiki.org/wiki/Extension:Favorites", |
| 28 | +); |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | + |
| 33 | +$dir = dirname(__FILE__) . '/'; |
| 34 | +$wgExtensionMessagesFiles['Favorites'] = $dir . 'favorites.i18n.php'; |
| 35 | +$wgAutoloadClasses['Favorites'] = $dir . 'Favorites_body.php'; |
| 36 | +$wgAutoloadClasses['FavoritelistEditor'] = $dir . 'FavoritelistEditor.php'; |
| 37 | +$wgAutoloadClasses['FavoritedItem'] = $dir . 'FavoritedItem.php'; |
| 38 | +$wgAutoloadClasses['SpecialFavoritelist'] = $dir . 'SpecialFavoritelist.php'; |
| 39 | +$wgAutoloadClasses['APIQueryFavoritelistRaw'] = $dir . 'APIQueryFavoritelistRaw.php'; |
| 40 | +$wgAutoloadClasses['APIFavorite'] = $dir . 'APIFavorite.php'; |
| 41 | +$wgAutoloadClasses['FavUser'] = $dir . 'FavUser.php'; |
| 42 | +$wgAutoloadClasses['FavArticle'] = $dir . 'FavArticle.php'; |
| 43 | +$wgAutoloadClasses['FavTitle'] = $dir . 'FavTitle.php'; |
| 44 | +$wgAutoloadClasses['FavParser'] = $dir . 'FavParser.php'; |
| 45 | +$wgHooks['LoadExtensionSchemaUpdates'][] = 'FavSQLUpdate'; |
| 46 | +$wgSpecialPages['Favoritelist'] = 'SpecialFavoritelist'; |
| 47 | +$wgSpecialPageGroups['Favoritelist'] = 'other'; |
| 48 | + |
| 49 | + |
| 50 | +//tag hook |
| 51 | +$wgHooks['ParserFirstCallInit'][] = 'ParseFavorites'; |
| 52 | + |
| 53 | + |
| 54 | +//add the icon / link |
| 55 | +$wgHooks['SkinTemplateNavigation'][] = 'fnNavUrls'; |
| 56 | + |
| 57 | +//add or remove |
| 58 | +$wgHooks['UnknownAction'][] = 'fnAction'; |
| 59 | + |
| 60 | +//handle page moves |
| 61 | +$wgHooks['TitleMoveComplete'][] = 'fnHookMoveToFav'; |
| 62 | + |
| 63 | +//add CSS |
| 64 | +$wgHooks['BeforePageDisplay'][] = 'fnAddCss'; |
| 65 | + |
| 66 | + |
| 67 | +function fnAction ($action, $article) { |
| 68 | + $title = new Title(); |
| 69 | + $favArticle = new FavArticle($title); |
| 70 | + |
| 71 | + if ($action == 'unfavorite') { |
| 72 | + $favArticle->unfavorite($action, $article); |
| 73 | + |
| 74 | + } elseif ($action == 'favorite') { |
| 75 | + $favArticle->favorite($action, $article); |
| 76 | + } else { |
| 77 | + return true; |
| 78 | + } |
| 79 | + return false; |
| 80 | +} |
| 81 | + |
| 82 | +function fnNavUrls(&$sktemplate, &$links) { |
| 83 | + $fNav = new Favorites(); |
| 84 | + $fNav->favoritesIcon($sktemplate, $links); |
| 85 | + return true; |
| 86 | +} |
| 87 | + |
| 88 | +function fnHookMoveToFav(&$title, &$nt, &$wgUser, $pageid, $redirid ) { |
| 89 | + $favTitle = new FavTitle(); |
| 90 | + $favTitle->moveToFav($title, $nt, $wgUser, $pageid, $redirid ); |
| 91 | + return true; |
| 92 | +} |
| 93 | + |
| 94 | +function fnAddCss (&$out) { |
| 95 | + global $wgScriptPath; |
| 96 | + $out->addStyle($wgScriptPath. '/extensions/favorites/favorites.css'); |
| 97 | + //$out->addInlineScript($wgScriptPath . '/extensions/favorites/ajaxfavorite.js'); |
| 98 | + return true; |
| 99 | +} |
| 100 | + |
| 101 | +function ParseFavorites(Parser &$parser) { |
| 102 | + |
| 103 | + $parser->setHook( 'favorites', 'favParser_Render' ); |
| 104 | + |
| 105 | + return true; |
| 106 | +} |
| 107 | + |
| 108 | + |
| 109 | +$markerList = array(); |
| 110 | +function favParser_Render ( $input, $argv, $parser) { |
| 111 | + # The parser function itself |
| 112 | + # The input parameters are wikitext with templates expanded |
| 113 | + # The output should be wikitext too |
| 114 | + $output = "Parser Output goes here."; |
| 115 | + |
| 116 | + $favParse = new FavParser(); |
| 117 | + $output = $favParse->wfSpecialFavoritelist(); |
| 118 | + $parser->disableCache(); |
| 119 | + return $output; |
| 120 | + |
| 121 | +} |
| 122 | + |
| 123 | + |
| 124 | +# Schema updates for update.php |
| 125 | +function FavSQLUpdate( $updater = null ) { |
| 126 | + if ( $updater === null ) { |
| 127 | + // <= 1.16 support |
| 128 | + global $wgExtNewTables, $wgExtModifiedFields; |
| 129 | + $wgExtNewTables[] = array( |
| 130 | + 'favoritelist', |
| 131 | + dirname( __FILE__ ) . '/favorites.sql' |
| 132 | + ); |
| 133 | + |
| 134 | + } else { |
| 135 | + // >= 1.17 support |
| 136 | + $updater->addExtensionUpdate( array( 'addTable', 'favoritelist', |
| 137 | + dirname( __FILE__ ) . '/favorites.sql', true ) ); |
| 138 | + |
| 139 | + } |
| 140 | + return true; |
| 141 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/Favorites/Favorites.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 142 | + native |
Index: trunk/extensions/Favorites/favorites.css |
— | — | @@ -0,0 +1,43 @@ |
| 2 | + |
| 3 | +/* Favorite/Unfavorite Icon Styling */ |
| 4 | + |
| 5 | +#ca-unfavorite.icon, |
| 6 | +#ca-favorite.icon { |
| 7 | + margin-right:3px; |
| 8 | + margin-left:0px; |
| 9 | +} |
| 10 | +#ca-unfavorite.icon a, |
| 11 | +#ca-favorite.icon a { |
| 12 | + margin: 0; |
| 13 | + padding: 0; |
| 14 | + outline: none; |
| 15 | + display: block; |
| 16 | + width: 22px; |
| 17 | + height: 2.5em; |
| 18 | + |
| 19 | +} |
| 20 | +#ca-favorite.icon a { |
| 21 | + background-image: url(images/fav-icons.png); |
| 22 | + background-position: 3px 60%; |
| 23 | +} |
| 24 | +#ca-unfavorite.icon a { |
| 25 | + background-image: url(images/fav-icons.png); |
| 26 | + background-position: -45px 60%; |
| 27 | +} |
| 28 | +#ca-favorite.icon a:hover { |
| 29 | + background-image: url(images/fav-icons.png); |
| 30 | + background-position: -45px 60%; |
| 31 | +} |
| 32 | +#ca-unfavorite.icon a:hover { |
| 33 | + background-image: url(images/fav-icons.png); |
| 34 | + background-position: 3px 60%; |
| 35 | +} |
| 36 | +#ca-unfavorite.icon a.loading, |
| 37 | +#ca-favorite.icon a.loading { |
| 38 | + background-image: url(images/fav-icon-loading.gif); |
| 39 | + background-position: center 60%; |
| 40 | +} |
| 41 | +#ca-unfavorite.icon a span, |
| 42 | +#ca-favorite.icon a span { |
| 43 | + display: none; |
| 44 | +} |
Property changes on: trunk/extensions/Favorites/favorites.css |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 45 | + native |