r97058 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r97057‎ | r97058 | r97059 >
Date:15:02, 14 September 2011
Author:ashley
Status:deferred
Tags:
Comment:
phase II social tools: BlogPage. Tested and built against MW 1.16.0, ResourceLoader support is there but it hasn't been tested so it probably is buggy (i.e. using addModules() instead of addModuleScripts()/addModuleStyles()), but I'll fix that later.
Modified paths:
  • /trunk/extensions/BlogPage (added) (history)
  • /trunk/extensions/BlogPage/ArticlesHome.css (added) (history)
  • /trunk/extensions/BlogPage/Blog.alias.php (added) (history)
  • /trunk/extensions/BlogPage/Blog.i18n.php (added) (history)
  • /trunk/extensions/BlogPage/Blog.namespaces.php (added) (history)
  • /trunk/extensions/BlogPage/Blog.php (added) (history)
  • /trunk/extensions/BlogPage/BlogHooks.php (added) (history)
  • /trunk/extensions/BlogPage/BlogPage.css (added) (history)
  • /trunk/extensions/BlogPage/BlogPage.php (added) (history)
  • /trunk/extensions/BlogPage/CreateBlogPost.css (added) (history)
  • /trunk/extensions/BlogPage/CreateBlogPost.js (added) (history)
  • /trunk/extensions/BlogPage/SpecialArticleLists.php (added) (history)
  • /trunk/extensions/BlogPage/SpecialArticlesHome.php (added) (history)
  • /trunk/extensions/BlogPage/SpecialCreateBlogPost.php (added) (history)
  • /trunk/extensions/BlogPage/TagCloudClass.php (added) (history)
  • /trunk/extensions/BlogPage/images (added) (history)
  • /trunk/extensions/BlogPage/images/comment.gif (added) (history)
  • /trunk/extensions/BlogPage/images/voteIcon.gif (added) (history)

Diff [purge]

Index: trunk/extensions/BlogPage/Blog.alias.php
@@ -0,0 +1,23 @@
 2+<?php
 3+/**
 4+ * Aliases for the BlogPage extension.
 5+ *
 6+ * @file
 7+ * @ingroup Extensions
 8+ */
 9+
 10+$aliases = array();
 11+
 12+/** English */
 13+$aliases['en'] = array(
 14+ 'ArticlesHome' => array( 'ArticlesHome' ),
 15+ 'ArticleLists' => array( 'ArticleLists' ),
 16+ 'CreateBlogPost' => array( 'CreateBlogPost' ),
 17+);
 18+
 19+/** Finnish (Suomi) */
 20+$aliases['fi'] = array(
 21+ 'ArticlesHome' => array( 'Artikkelien kotisivu' ),
 22+ 'ArticleLists' => array( 'Artikkelilistat' ),
 23+ 'CreateBlogPost' => array( 'Luo blogikirjoitus' ),
 24+);
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/Blog.alias.php
___________________________________________________________________
Added: svn:eol-style
125 + native
Index: trunk/extensions/BlogPage/CreateBlogPost.css
@@ -0,0 +1,38 @@
 2+/* CSS for Special:CreateBlogPost */
 3+.create-title {
 4+ margin: 0px 0px 5px;
 5+ color: rgb(51, 51, 51);
 6+ font-size: 14px;
 7+ font-weight: bold;
 8+}
 9+
 10+.categorytext {
 11+ width: 700px;
 12+}
 13+
 14+/* Copyright stuff below the form, right above the submit button */
 15+.copyright-warning {
 16+ margin: 0px 0px 10px;
 17+ width: 700px;
 18+ color: rgb(51, 51, 51);
 19+ font-size: 11px;
 20+}
 21+
 22+#editform .title {
 23+ margin: 0px 0px 5px;
 24+ color: rgb(120, 186, 93);
 25+ font-size: 1.1em;
 26+ font-weight: bold;
 27+}
 28+
 29+/* Category tag cloud */
 30+#create-tagcloud {
 31+ line-height: 25pt;
 32+ padding-bottom: 15px;
 33+ width: 600px;
 34+}
 35+
 36+a.tag-cloud-entry {
 37+ cursor: pointer;
 38+ text-decoration: underline;
 39+}
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/CreateBlogPost.css
___________________________________________________________________
Added: svn:eol-style
140 + native
Index: trunk/extensions/BlogPage/SpecialCreateBlogPost.php
@@ -0,0 +1,368 @@
 2+<?php
 3+/**
 4+ * A special page to create new blog posts (pages in the NS_BLOG namespace).
 5+ * Based on the CreateForms extension by Aaron Wright and David Pean.
 6+ *
 7+ * @file
 8+ * @ingroup Extensions
 9+ * @date 16 July 2011
 10+ */
 11+class SpecialCreateBlogPost extends SpecialPage {
 12+
 13+ public $tabCounter = 1;
 14+
 15+ /**
 16+ * Constructor -- set up the new special page
 17+ */
 18+ public function __construct() {
 19+ parent::__construct( 'CreateBlogPost', 'createblogpost' );
 20+ }
 21+
 22+ /**
 23+ * Show the special page
 24+ *
 25+ * @param $par Mixed: parameter passed to the special page or null
 26+ */
 27+ public function execute( $par ) {
 28+ global $wgOut, $wgUser, $wgRequest, $wgContLang, $wgScriptPath, $wgHooks;
 29+
 30+ // If the user can't create blog posts, display an error
 31+ if( !$wgUser->isAllowed( 'createblogpost' ) ) {
 32+ $wgOut->permissionRequired( 'createblogpost' );
 33+ return;
 34+ }
 35+
 36+ // Show a message if the database is in read-only mode
 37+ if ( wfReadOnly() ) {
 38+ $wgOut->readOnlyPage();
 39+ return;
 40+ }
 41+
 42+ // If user is blocked, s/he doesn't need to access this page
 43+ if( $wgUser->isBlocked() ) {
 44+ $wgOut->blockedPage( false );
 45+ return false;
 46+ }
 47+
 48+ // Set page title, robot policies, etc.
 49+ $this->setHeaders();
 50+
 51+ // i18n for JS
 52+ $wgHooks['MakeGlobalVariablesScript'][] = 'SpecialCreateBlogPost::addJSGlobals';
 53+
 54+ // Add CSS & JS
 55+ if ( defined( 'MW_SUPPORTS_RESOURCE_MODULES' ) ) {
 56+ $wgOut->addModules( array(
 57+ 'mediawiki.legacy.edit', 'ext.blogPage.create'
 58+ ) );
 59+ } else {
 60+ $wgOut->addExtensionStyle( $wgScriptPath . '/extensions/BlogPage/CreateBlogPost.css' );
 61+ $wgOut->addScriptFile( $wgScriptPath . '/extensions/BlogPage/CreateBlogPost.js' );
 62+ $wgOut->addScriptFile( 'edit.js' ); // for the edit toolbar
 63+ }
 64+
 65+ // If the request was POSTed, we haven't submitted a request yet AND
 66+ // we have a title, create the page...otherwise just display the
 67+ // creation form
 68+ if(
 69+ $wgRequest->wasPosted() &&
 70+ $_SESSION['alreadysubmitted'] == false
 71+ )
 72+ {
 73+ $_SESSION['alreadysubmitted'] = true;
 74+
 75+ // Protect against cross-site request forgery (CSRF)
 76+ if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
 77+ $wgOut->addHTML( wfMsg( 'sessionfailure' ) );
 78+ return;
 79+ }
 80+
 81+ // Create a Title object, or try to, anyway
 82+ $userSuppliedTitle = $wgRequest->getVal( 'title2' );
 83+ $title = Title::makeTitleSafe( NS_BLOG, $userSuppliedTitle );
 84+
 85+ // @todo CHECKME: are these still needed? The JS performs these
 86+ // checks already but then again JS is also easy to fool...
 87+
 88+ // The user didn't supply a title? Ask them to supply one.
 89+ if ( !$userSuppliedTitle ) {
 90+ $wgOut->setPageTitle( wfMsg( 'errorpagetitle' ) );
 91+ $wgOut->addWikiMsg( 'blog-create-error-need-title' );
 92+ $wgOut->addReturnTo( $this->getTitle() );
 93+ return;
 94+ }
 95+
 96+ // The user didn't supply the blog post text? Ask them to supply it.
 97+ if ( !$wgRequest->getVal( 'pageBody' ) ) {
 98+ $wgOut->setPageTitle( wfMsg( 'errorpagetitle' ) );
 99+ $wgOut->addWikiMsg( 'blog-create-error-need-content' );
 100+ $wgOut->addReturnTo( $this->getTitle() );
 101+ return;
 102+ }
 103+
 104+ // Localized variables that will be used when creating the page
 105+ $localizedCatNS = $wgContLang->getNsText( NS_CATEGORY );
 106+ $today = $wgContLang->date( wfTimestampNow() );
 107+
 108+ // Create the blog page if it doesn't already exist
 109+ $article = new Article( $title, 0 );
 110+ if ( $article->exists() ) {
 111+ $wgOut->setPageTitle( wfMsg( 'errorpagetitle' ) );
 112+ $wgOut->addWikiMsg( 'blog-create-error-page-exists' );
 113+ $wgOut->addReturnTo( $this->getTitle() );
 114+ return;
 115+ } else {
 116+ // The blog post will be by default categorized into two
 117+ // categories, "Articles by User $1" and "(today's date)",
 118+ // but the user may supply some categories themselves, so
 119+ // we need to take those into account, too.
 120+ $categories = array(
 121+ '[[' . $localizedCatNS . ':' .
 122+ wfMsgForContent(
 123+ 'blog-by-user-category',
 124+ wfMsgForContent( 'blog-category' )
 125+ ) . wfMsgForContent( 'word-separator' ) .
 126+ $wgUser->getName() . ']]',
 127+ "[[{$localizedCatNS}:{$today}]]"
 128+ );
 129+
 130+ $userSuppliedCategories = $wgRequest->getVal( 'pageCtg' );
 131+ if ( !empty( $userSuppliedCategories ) ) {
 132+ // Explode along commas so that we will have an array that
 133+ // we can loop over
 134+ $userSuppliedCategories = explode( ',', $userSuppliedCategories );
 135+ foreach( $userSuppliedCategories as $cat ) {
 136+ $cat = trim( $cat ); // GTFO@excess whitespace
 137+ if ( !empty( $cat ) ) {
 138+ $categories[] = "[[{$localizedCatNS}:{$cat}]]";
 139+ }
 140+ }
 141+ }
 142+
 143+ // Convert the array into a string
 144+ $wikitextCategories = implode( "\n", $categories );
 145+
 146+ // Perform the edit
 147+ $article->doEdit(
 148+ // Instead of <vote />, Wikia had Template:Blog Top over
 149+ // here and Template:Blog Bottom at the bottom, where we
 150+ // have the comments tag right now
 151+ '<vote />' . "\n" . '<!--start text-->' . "\n" .
 152+ $wgRequest->getVal( 'pageBody' ) . "\n\n" .
 153+ '<comments />' . "\n\n" . $wikitextCategories .
 154+ "\n__NOEDITSECTION__",
 155+ wfMsgForContent( 'blog-create-summary' )
 156+ );
 157+
 158+ $articleId = $article->getID();
 159+ // Add a vote for the page
 160+ // This was originally in its own global function,
 161+ // wfFinishCreateBlog and after that in the BlogHooks class but
 162+ // it just wouldn't work with Special:CreateBlogPost so I
 163+ // decided to move it here since this is supposed to be like
 164+ // the primary way of creating new blog posts...
 165+ // Using OutputPageBeforeHTML hook, which, according to its
 166+ // manual page, runs on *every* page view was such a stupid
 167+ // idea IMHO.
 168+ $vote = new Vote( $articleId );
 169+ $vote->insert( 1 );
 170+
 171+ $stats = new UserStatsTrack( $wgUser->getID(), $wgUser->getName() );
 172+ $stats->updateWeeklyPoints( $stats->point_values['opinions_created'] );
 173+ $stats->updateMonthlyPoints( $stats->point_values['opinions_created'] );
 174+ //if( $wgEnableFacebook ) {
 175+ // BlogHooks::updateFacebookProfile();
 176+ //}
 177+ //if( $wgSendNewArticleToFriends ) {
 178+ // $invite = SpecialPage::getTitleFor( 'EmailNewArticle' );
 179+ // $wgOut->redirect(
 180+ // $invite->getFullURL( 'page=' . $title->getPrefixedText() )
 181+ // );
 182+ //}
 183+
 184+ // Redirect the user to the new blog post they just created
 185+ $wgOut->redirect( $title->getFullURL() );
 186+ }
 187+ } else {
 188+ $_SESSION['alreadysubmitted'] = false;
 189+
 190+ // Start building the HTML
 191+ $output = '';
 192+
 193+ // Show the blog rules, if the message containing them ain't empty
 194+ $message = trim( wfMsgExt( 'blog-create-rules', array( 'parse', 'content' ) ) );
 195+ // Yes, the strlen() is needed, I dunno why wfEmptyMsg() won't work
 196+ if( !wfEmptyMsg( 'blog-create-rules', $message ) && strlen( $message ) > 0 ) {
 197+ $output .= $message . '<br />';
 198+ }
 199+
 200+ // Main form
 201+ $output .= $this->displayForm();
 202+
 203+ // Show everything to the user
 204+ $wgOut->addHTML( $output );
 205+ }
 206+ }
 207+
 208+ /**
 209+ * Show the input field where the user can enter the blog post title.
 210+ * @return String: HTML
 211+ */
 212+ function displayFormPageTitle() {
 213+ $output = '<span class="create-title">' . wfMsg( 'blog-create-title' ) .
 214+ '</span><br /><input class="createbox" type="text" tabindex="' .
 215+ $this->tabCounter . '" name="title2" id="title" style="width: 500px;"><br /><br />';
 216+ $this->tabCounter++;
 217+ return $output;
 218+ }
 219+
 220+ /**
 221+ * Show the input field where the user can enter the blog post body.
 222+ * @return String: HTML
 223+ */
 224+ function displayFormPageText() {
 225+ $output = '<span class="create-title">' . wfMsg( 'blog-create-text' ) .
 226+ '</span><br />';
 227+ // The EditPage toolbar wasn't originally present here but I figured
 228+ // that adding it might be more helpful than anything else.
 229+ $output .= EditPage::getEditToolbar();
 230+ $output .= '<textarea class="createbox" tabindex="' .
 231+ $this->tabCounter . '" accesskey="," name="pageBody" id="pageBody" rows="10" cols="80"></textarea><br /><br />';
 232+ $this->tabCounter++;
 233+ return $output;
 234+ }
 235+
 236+ /**
 237+ * Show the category cloud.
 238+ * @return String: HTML
 239+ */
 240+ function displayFormPageCategories() {
 241+ $cloud = new BlogTagCloud( 20 );
 242+
 243+ $tagcloud = '<div id="create-tagcloud">';
 244+ $tagnumber = 0;
 245+ foreach ( $cloud->tags as $tag => $att ) {
 246+ $tag = trim( $tag );
 247+ $blogUserCat = wfMsgForContent( 'blog-by-user-category',
 248+ wfMsgForContent( 'blog-category' ) );
 249+ // Ignore "Articles by User X" categories
 250+ if ( !preg_match( '/' . $blogUserCat . '/', $tag ) ) {
 251+ $slashedTag = $tag; // define variable
 252+ // Fix for categories that contain an apostrophe
 253+ if ( strpos( $tag, "'" ) ) {
 254+ $slashedTag = str_replace( "'", "\'", $tag );
 255+ }
 256+ $tagcloud .= " <span id=\"tag-{$tagnumber}\" style=\"font-size:{$cloud->tags[$tag]['size']}{$cloud->tags_size_type}\">
 257+ <a class=\"tag-cloud-entry\" onclick=\"javascript:CreateBlogPost.insertTag('" . $slashedTag . "',{$tagnumber});\">{$tag}</a>
 258+ </span>";
 259+ $tagnumber++;
 260+ }
 261+ }
 262+ $tagcloud .= '</div>';
 263+
 264+ $output = '<div class="create-title">' .
 265+ wfMsg( 'blog-create-categories' ) .
 266+ '</div>
 267+ <div class="categorytext">' .
 268+ wfMsg( 'blog-create-category-help' ) .
 269+ '</div>' . "\n";
 270+ $output .= $tagcloud . "\n";
 271+ $output .= '<textarea class="createbox" tabindex="' . $this->tabCounter .
 272+ '" accesskey="," name="pageCtg" id="pageCtg" rows="2" cols="80"></textarea><br /><br />';
 273+ $this->tabCounter++;
 274+
 275+ return $output;
 276+ }
 277+
 278+ /**
 279+ * Display the standard copyright notice that is shown on normal edit page,
 280+ * on the upload form etc.
 281+ *
 282+ * @return String: HTML
 283+ */
 284+ function displayCopyrightWarning() {
 285+ global $wgRightsText;
 286+ if ( $wgRightsText ) {
 287+ $copywarnMsg = 'copyrightwarning';
 288+ $copywarnMsgParams = array(
 289+ '[[' . wfMsgForContent( 'copyrightpage' ) . ']]',
 290+ $wgRightsText
 291+ );
 292+ } else {
 293+ $copywarnMsg = 'copyrightwarning2';
 294+ $copywarnMsgParams = array(
 295+ '[[' . wfMsgForContent( 'copyrightpage' ) . ']]'
 296+ );
 297+ }
 298+ return '<div class="copyright-warning">' .
 299+ wfMsgExt( $copywarnMsg, 'parse', $copywarnMsgParams ) .
 300+ '</div>';
 301+ }
 302+
 303+ /**
 304+ * Show the form for creating new blog posts.
 305+ * @return String: HTML
 306+ */
 307+ function displayForm() {
 308+ global $wgUser;
 309+
 310+ $output = '<form id="editform" name="editform" method="post" action="' .
 311+ $this->getTitle()->escapeFullURL() . '" enctype="multipart/form-data">';
 312+ $output .= "\n" . $this->displayFormPageTitle() . "\n";
 313+ $output .= "\n" . $this->displayFormPageText() . "\n";
 314+
 315+ $output .= "\n" . $this->displayFormPageCategories() . "\n";
 316+ $output .= "\n" . $this->displayCopyrightWarning() . "\n";
 317+ $output .= '<input type="button" onclick="CreateBlogPost.performChecks()" value="' .
 318+ wfMsg( 'blog-create-button' ) . '" name="wpSave" class="createsubmit site-button" accesskey="s" title="' .
 319+ wfMsg( 'tooltip-save' ) . ' [alt-s]" />
 320+ <input type="hidden" value="" name="wpSection" />
 321+ <input type="hidden" value="" name="wpEdittime" />
 322+ <input type="hidden" value="" name="wpTextbox1" id="wpTextbox1" />
 323+ <input type="hidden" value="' . htmlspecialchars( $wgUser->editToken() ) .
 324+ '" name="wpEditToken" />';
 325+ $output .= "\n" . '</form>' . "\n";
 326+
 327+ return $output;
 328+ }
 329+
 330+ /**
 331+ * Check if there is already a blog post with the given title.
 332+ *
 333+ * @param $pageName String: page title to check
 334+ * @return String: 'OK' when there isn't such a page, else 'Page exists'
 335+ */
 336+ public static function checkTitleExistence( $pageName ) {
 337+ // Construct page title object to convert to database key
 338+ $pageTitle = Title::makeTitle( NS_MAIN, urldecode( $pageName ) );
 339+ $dbKey = $pageTitle->getDBkey();
 340+
 341+ // Database key would be in page title if the page already exists
 342+ $dbr = wfGetDB( DB_MASTER );
 343+ $s = $dbr->selectRow(
 344+ 'page',
 345+ array( 'page_id' ),
 346+ array( 'page_title' => $dbKey, 'page_namespace' => NS_BLOG ),
 347+ __METHOD__
 348+ );
 349+
 350+ if ( $s !== false ) {
 351+ return 'Page exists';
 352+ } else {
 353+ return 'OK';
 354+ }
 355+ }
 356+
 357+ /**
 358+ * Add i18n messages for the JS file.
 359+ *
 360+ * @param $vars Array: array of pre-existing JavaScript globals
 361+ * @return Boolean: true
 362+ */
 363+ public static function addJSGlobals( $vars ) {
 364+ $vars['_BLOG_NEEDS_CONTENT'] = wfMsg( 'blog-js-create-error-need-content' );
 365+ $vars['_BLOG_NEEDS_TITLE'] = wfMsg( 'blog-js-create-error-need-title' );
 366+ $vars['_BLOG_PAGE_EXISTS'] = wfMsg( 'blog-js-create-error-page-exists' );
 367+ return true;
 368+ }
 369+}
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/SpecialCreateBlogPost.php
___________________________________________________________________
Added: svn:eol-style
1370 + native
Index: trunk/extensions/BlogPage/images/comment.gif
Cannot display: file marked as a binary type.
svn:mime-type = image/gif
Property changes on: trunk/extensions/BlogPage/images/comment.gif
___________________________________________________________________
Added: svn:mime-type
2371 + image/gif
Index: trunk/extensions/BlogPage/images/voteIcon.gif
Cannot display: file marked as a binary type.
svn:mime-type = image/gif
Property changes on: trunk/extensions/BlogPage/images/voteIcon.gif
___________________________________________________________________
Added: svn:mime-type
3372 + image/gif
Index: trunk/extensions/BlogPage/ArticlesHome.css
@@ -0,0 +1,251 @@
 2+/* Articles Home */
 3+/* Fix the issue with "X votes" boxes on Special:ArticlesHome --
 4+NYC skins already include this class definition, as do some social tools, but
 5+core skins do not */
 6+.cleared {
 7+ clear: both;
 8+}
 9+
 10+h1.firstHeading {
 11+ display: none;
 12+}
 13+
 14+#main {
 15+ padding: 0px !important;
 16+}
 17+
 18+.main-page-left {
 19+ float: left;
 20+ width: 60%;
 21+ padding: 10px 0px 0px 0px;
 22+}
 23+
 24+.main-page-right {
 25+ float: right;
 26+ width: 35%;
 27+ padding: 10px 0px 0px 0px;
 28+}
 29+
 30+.main-page-sub-links a {
 31+ color: #797979;
 32+}
 33+
 34+.side-articles {
 35+ margin: 0px 0px 25px 0px;
 36+}
 37+
 38+.side-articles h2 {
 39+ font-size: 1.5em;
 40+ padding: 0px 0px 5px 0px;
 41+ margin: 0px 0px 10px 0px !important;
 42+ border-bottom: 1px solid #dcdcdc !important;
 43+}
 44+
 45+.side-articles .listpageItem {
 46+ padding: 0px 0px 0px 0px;
 47+ margin: 0px 0px 0px 0px;
 48+ font-size: 11px;
 49+ width: 350px;
 50+}
 51+
 52+.side-articles .showdetails a {
 53+ font-size: 12px;
 54+}
 55+
 56+/* Logged-In Articles */
 57+.logged-in-articles {
 58+ margin: 0px 0px 18px 0px;
 59+}
 60+
 61+.logged-in-articles h2 {
 62+ font-size: 1.5em;
 63+ margin: 0px 0px 10px 0px;
 64+ padding: 0px 0px 3px 0px;
 65+ border-bottom: 1px solid #dcdcdc;
 66+}
 67+
 68+.logged-in-articles p {
 69+ margin: 10px 0px 10px 0px !important;
 70+}
 71+
 72+.logged-in-articles .listpageItem {
 73+ padding: 0px 0px 10px 0px;
 74+ margin: 0px 0px 10px 0px;
 75+ width: 600px;
 76+}
 77+
 78+.rss-feed {
 79+ color: #797979;
 80+ font-size: 10px;
 81+ vertical-align: middle;
 82+ margin: -1px 0px 0px 5px;
 83+}
 84+
 85+.rss-feed img {
 86+ vertical-align: middle;
 87+ margin: 0px 3px 0px 0px;
 88+}
 89+
 90+.side-articles .listpages-item {
 91+ margin-top: 5px !important;
 92+}
 93+
 94+.side-articles a {
 95+ font-size: 12px !important;
 96+}
 97+
 98+.main-page-left .listpages-item a {
 99+ font-size: 16px;
 100+}
 101+
 102+.listpages-blurb {
 103+ font-size: 11px !important;
 104+ line-height: 14px !important;
 105+}
 106+
 107+.main-page-left .listpages-item .listpages-blurb a {
 108+ font-size: 11px !important;
 109+}
 110+/* The following has been copypasted from Sports.css (the main CSS file for the ArmchairGM skin) */
 111+/* ListPages */
 112+.listpages-left {
 113+ float: left;
 114+ border: 1px solid #dcdcdc;
 115+ width: 500px;
 116+}
 117+
 118+.listpages-link {
 119+ margin: 0px;
 120+ padding: 0px;
 121+}
 122+
 123+.listpages-item {
 124+ margin-top: 10px;
 125+}
 126+
 127+.listpages-item a {
 128+ text-decoration: underline;
 129+ font-weight: bold;
 130+}
 131+
 132+.listpages-image {
 133+ float: left;
 134+ margin-right: 10px;
 135+}
 136+
 137+.listpages-categories {
 138+ color: #78BA5D;
 139+ font-size: 10px;
 140+ font-weight: bold;
 141+}
 142+
 143+.listpages-categories a {
 144+ color: #78BA5D;
 145+ font-size: 10px;
 146+ text-decoration: none;
 147+ border-bottom: 1px dotted #78BA5D;
 148+}
 149+
 150+.listpages-date {
 151+ color: #888;
 152+ font-size: 10px;
 153+}
 154+
 155+.listpages-stats {
 156+ color: #666;
 157+ font-weight: bold;
 158+ font-size: 11px;
 159+}
 160+
 161+.listpages-stats img {
 162+ vertical-align: middle;
 163+ margin: -2px 1px 0px 2px;
 164+}
 165+
 166+.listpages-votebox {
 167+ float: left;
 168+ margin: 0px 10px 3px 0px;
 169+ text-align: center;
 170+ width: 25px;
 171+}
 172+
 173+.rating-total {
 174+ color: #666;
 175+ font-weight: bold;
 176+ font-size: 11px;
 177+}
 178+
 179+.listpages-votebox-number {
 180+ background-color: #89C46F;
 181+ color: #FFFFFF;
 182+ padding: 3px 0px 0px;
 183+ margin: 0px 0px 2px 0px;
 184+ font-weight: bold;
 185+ height: 22px;
 186+}
 187+
 188+.listpages-votebox-text {
 189+ margin: -1px 0px 0px 0px;
 190+ font-size: 9px;
 191+ color: #777;
 192+ line-height: 9px;
 193+}
 194+
 195+.listpages-commentbox-number {
 196+ background-color: orange;
 197+ color: #FFFFFF;
 198+ font-weight: bold;
 199+ height: 22px;
 200+ margin: 0px 0px 2px;
 201+ padding: 3px 0px 0px;
 202+}
 203+
 204+.listpages-blurb {
 205+ margin: 0px 0px 2px 0px;
 206+}
 207+
 208+.listpages-blurb a {
 209+ font-size: 11px;
 210+ text-decoration: underline;
 211+}
 212+
 213+.listpages-nav-buttons {
 214+ margin: 10px 0px 0px 0px;
 215+}
 216+
 217+.listpages-blurb-size-small {
 218+ font-size: 11px;
 219+ line-height: 15px;
 220+}
 221+
 222+.listpages-blurb-size-medium {
 223+ font-size: 12px;
 224+ line-height: 16px;
 225+}
 226+
 227+.listpages-blurb-size-large {
 228+ font-size: 13px;
 229+ line-height: 17px;
 230+}
 231+
 232+.listpage-button {
 233+ background-color: #FAFAFA;
 234+ border: 1px solid #DCDCDC;
 235+ color: #376EA6;
 236+ font-size: 12px;
 237+ padding: 3px;
 238+ margin: 0px 3px 0px 0px;
 239+}
 240+
 241+.listpage-button-off {
 242+ background-color: #FAFAFA;
 243+ border: 1px solid #DCDCDC;
 244+ color: #797979;
 245+ font-size: 12px;
 246+ padding: 3px;
 247+ margin: 0px 3px 0px 0px;
 248+}
 249+
 250+.listpages-nav-buttons a {
 251+ margin: 0px 5px 0px 0px;
 252+}
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/ArticlesHome.css
___________________________________________________________________
Added: svn:eol-style
1253 + native
Index: trunk/extensions/BlogPage/BlogPage.php
@@ -0,0 +1,1262 @@
 2+<?php
 3+/**
 4+ * Class for handling the viewing of pages in the NS_BLOG namespace.
 5+ *
 6+ * @file
 7+ */
 8+class BlogPage extends Article {
 9+
 10+ var $title = null;
 11+ var $authors = array();
 12+
 13+ function __construct( Title $title ) {
 14+ parent::__construct( $title );
 15+ $this->setContent();
 16+ $this->getAuthors();
 17+ }
 18+
 19+ function setContent() {
 20+ // Get the page content for later use
 21+ $this->pageContent = $this->getContent();
 22+
 23+ // If it's a redirect, in order to get the *real* content for later use,
 24+ // we have to load the text for the real page
 25+ // Note: If $this->getContent() is called anywhere before parent::view,
 26+ // the real article text won't get loaded on the page
 27+ if( $this->isRedirect( $this->pageContent ) ) {
 28+ wfDebugLog( 'BlogPage', __METHOD__ );
 29+
 30+ $target = $this->followRedirect();
 31+ $rarticle = new Article( $target );
 32+ $this->pageContent = $rarticle->getContent();
 33+
 34+ // If we don't clear, the page content will be [[redirect-blah]],
 35+ // and not the actual page
 36+ $this->clear();
 37+ }
 38+ }
 39+
 40+ function view() {
 41+ global $wgOut, $wgUser, $wgTitle, $wgBlogPageDisplay;
 42+
 43+ wfProfileIn( __METHOD__ );
 44+
 45+ $sk = $wgUser->getSkin();
 46+
 47+ wfDebugLog( 'BlogPage', __METHOD__ );
 48+
 49+ $wgOut->setHTMLTitle( $wgTitle->getText() );
 50+ $wgOut->setPageTitle( $wgTitle->getText() );
 51+
 52+ // Don't throw a bunch of E_NOTICEs when we're viewing the page of a
 53+ // nonexistent blog post
 54+ if ( !$this->getID() ) {
 55+ parent::view();
 56+ return '';
 57+ }
 58+
 59+ $wgOut->addHTML( "\t\t" . '<div id="blog-page-container">' . "\n" );
 60+
 61+ if ( $wgBlogPageDisplay['leftcolumn'] == true ) {
 62+ $wgOut->addHTML( "\t\t\t" . '<div id="blog-page-left">' . "\n" );
 63+
 64+ $wgOut->addHTML( "\t\t\t\t" . '<div class="blog-left-units">' . "\n" );
 65+
 66+ $wgOut->addHTML(
 67+ "\t\t\t\t\t" . '<h2>' . wfMsgExt(
 68+ 'blog-author-title',
 69+ 'parsemag',
 70+ count( $this->authors ) ) . '</h2>' . "\n"
 71+ );
 72+ // Why was this commented out? --ashley, 11 July 2011
 73+ if( count( $this->authors ) > 1 ) {
 74+ $wgOut->addHTML( $this->displayMultipleAuthorsMessage() );
 75+ }
 76+
 77+ // Output each author's box in the order that they appear in [[Category:Opinions by X]]
 78+ for( $x = 0; $x <= count( $this->authors ); $x++ ) {
 79+ $wgOut->addHTML( $this->displayAuthorBox( $x ) );
 80+ }
 81+
 82+ $wgOut->addHTML( $this->recentEditors() );
 83+ $wgOut->addHTML( $this->recentVoters() );
 84+ $wgOut->addHTML( $this->embedWidget() );
 85+
 86+ $wgOut->addHTML( '</div>' . "\n" );
 87+
 88+ $wgOut->addHTML( $this->leftAdUnit() );
 89+ }
 90+
 91+ $wgOut->addHTML( "\t\t\t" . '</div><!-- #blog-page-left -->' . "\n" );
 92+
 93+ $wgOut->addHTML( '<div id="blog-page-middle">' . "\n" );
 94+ global $wgUseEditButtonFloat;
 95+ if( $wgUseEditButtonFloat == true && method_exists( $sk, 'editMenu' ) ) {
 96+ $wgOut->addHTML( $sk->editMenu() );
 97+ }
 98+ $wgOut->addHTML( "<h1 class=\"page-title\">{$wgTitle->getText()}</h1>\n" );
 99+ $wgOut->addHTML( $this->getByLine() );
 100+
 101+ $wgOut->addHTML( "\n<!--start Article::view-->\n" );
 102+ parent::view();
 103+
 104+ // Get categories
 105+ $cat = $sk->getCategoryLinks();
 106+ if( $cat ) {
 107+ $wgOut->addHTML( "\n<div id=\"catlinks\" class=\"catlinks\">{$cat}</div>\n" );
 108+ }
 109+
 110+ $wgOut->addHTML( "\n<!--end Article::view-->\n" );
 111+
 112+ $wgOut->addHTML( '</div>' . "\n" );
 113+
 114+ if ( $wgBlogPageDisplay['rightcolumn'] == true ) {
 115+ $wgOut->addHTML( '<div id="blog-page-right">' . "\n" );
 116+
 117+ $wgOut->addHTML( $this->getPopularArticles() );
 118+ $wgOut->addHTML( $this->getInTheNews() );
 119+ $wgOut->addHTML( $this->getCommentsOfTheDay() );
 120+ $wgOut->addHTML( $this->getRandomCasualGame() );
 121+ $wgOut->addHTML( $this->getNewArticles() );
 122+
 123+ $wgOut->addHTML( '</div>' . "\n" );
 124+ }
 125+
 126+ $wgOut->addHTML( '<div class="cleared"></div>' . "\n" );
 127+ $wgOut->addHTML( '</div><!-- #blog-page-container -->' . "\n" );
 128+
 129+ wfProfileOut( __METHOD__ );
 130+ }
 131+
 132+ /**
 133+ * Get the authors of this blog post and store them in the authors member
 134+ * variable.
 135+ */
 136+ function getAuthors() {
 137+ global $wgContLang;
 138+
 139+ $articleText = $this->pageContent;
 140+ $categoryName = $wgContLang->getNsText( NS_CATEGORY );
 141+ $blogCat = wfMsgForContent( 'blog-category' );
 142+
 143+ // This unbelievably weak and hacky regex is used to find out the
 144+ // author's name from the category. See also getBlurb(), which uses a
 145+ // similar regex.
 146+ preg_match_all(
 147+ "/\[\[(?:(?:c|C)ategory|{$categoryName}):\s?" .
 148+ wfMsgForContent( 'blog-by-user-category', $blogCat ) .
 149+ " (.*)\]\]/",
 150+ $articleText,
 151+ $matches
 152+ );
 153+ $authors = $matches[1];
 154+
 155+ foreach( $authors as $author ) {
 156+ $authorUserId = User::idFromName( $author );
 157+ $this->authors[] = array(
 158+ 'user_name' => trim( $author ),
 159+ 'user_id' => $authorUserId
 160+ );
 161+ }
 162+ }
 163+
 164+ /**
 165+ * Get the creation date of the page with the given ID from the revision
 166+ * table and cache it in memcached.
 167+ * The return value of this function can be passed to the various $wgLang
 168+ * methods for i18n-compatible code.
 169+ *
 170+ * @param $pageId Integer: page ID number
 171+ * @return Integer: page creation date
 172+ */
 173+ public static function getCreateDate( $pageId ) {
 174+ global $wgMemc;
 175+
 176+ // Try memcached first
 177+ $key = wfMemcKey( 'page', 'create_date', $pageId );
 178+ $data = $wgMemc->get( $key );
 179+
 180+ if( !$data ) {
 181+ wfDebugLog( 'BlogPage', "Loading create_date for page {$pageId} from database" );
 182+ $dbr = wfGetDB( DB_SLAVE );
 183+ $createDate = $dbr->selectField(
 184+ 'revision',
 185+ 'rev_timestamp',//'UNIX_TIMESTAMP(rev_timestamp) AS create_date',
 186+ array( 'rev_page' => $pageId ),
 187+ __METHOD__,
 188+ array( 'ORDER BY' => 'rev_timestamp ASC' )
 189+ );
 190+ $wgMemc->set( $key, $createDate );
 191+ } else {
 192+ wfDebugLog( 'BlogPage', "Loading create_date for page {$pageId} from cache" );
 193+ $createDate = $data;
 194+ }
 195+
 196+ return $createDate;
 197+ }
 198+
 199+ /**
 200+ * Get the last edit date of the page with the given ID from the revision
 201+ * table and cache it in memcached.
 202+ * The return value of this function can be passed to the various $wgLang
 203+ * methods for i18n-compatible code.
 204+ *
 205+ * @param $pageId Integer: page ID number
 206+ * @return Integer: page creation date
 207+ */
 208+ public static function getLastEditTimestamp( $pageId ) {
 209+ global $wgMemc;
 210+
 211+ // Try memcached first
 212+ $key = wfMemcKey( 'page', 'last_edit_date', $pageId );
 213+ $data = $wgMemc->get( $key );
 214+
 215+ if( !$data ) {
 216+ wfDebugLog( 'BlogPage', "Loading last_edit_date for page {$pageId} from database" );
 217+ $dbr = wfGetDB( DB_SLAVE );
 218+ $lastEditDate = $dbr->selectField(
 219+ 'revision',
 220+ 'MAX(rev_timestamp)',
 221+ array( 'rev_page' => $pageId ),
 222+ __METHOD__,
 223+ array( 'ORDER BY' => 'rev_timestamp ASC' )
 224+ );
 225+ $wgMemc->set( $key, $lastEditDate );
 226+ } else {
 227+ wfDebugLog( 'BlogPage', "Loading last_edit_date for page {$pageId} from cache" );
 228+ $lastEditDate = $data;
 229+ }
 230+
 231+ return $lastEditDate;
 232+ }
 233+
 234+ /**
 235+ * Get the "by X, Y and Z" line, which also contains other nifty
 236+ * information, such as the date of the last edit and the creation date.
 237+ *
 238+ * @return String
 239+ */
 240+ function getByLine() {
 241+ global $wgTitle, $wgLang;
 242+
 243+ $count = 0;
 244+
 245+ // Get date of last edit
 246+ /*
 247+ $year = substr( $wgTitle->getTouched(), 0, 4 );
 248+ $month = substr( $wgTitle->getTouched(), 4, 2 );
 249+ $day = substr( $wgTitle->getTouched(), 6, 2 );
 250+ $edit_date = date( 'F d, Y', mktime( 0, 0, 0, $month, $day, $year ) );
 251+ */
 252+ //$edit_date = $wgLang->timeanddate( $wgTitle->getTouched(), true );
 253+ // Title::getTouched() sucks, because apparently voting for a page
 254+ // invalidates page cache, too... --ashley, 7 August 2011
 255+ $edit_date = $wgLang->timeanddate(
 256+ self::getLastEditTimestamp( $wgTitle->getArticleID() ),
 257+ true
 258+ );
 259+
 260+ // Get date of when article was created
 261+ #$create_date = date( 'F d, Y', $this->getCreateDate( $wgTitle->getArticleID() ) );
 262+ $create_date = $wgLang->timeanddate(
 263+ self::getCreateDate( $wgTitle->getArticleID() ),
 264+ true
 265+ );
 266+
 267+ $output = '<div class="blog-byline">' . wfMsg( 'blog-by' ) . ' ';
 268+
 269+ $authors = '';
 270+ foreach( $this->authors as $author ) {
 271+ $count++;
 272+ $userTitle = Title::makeTitle( NS_USER, $author['user_name'] );
 273+ if ( $authors && count( $this->authors ) > 2 ) {
 274+ $authors .= ', ';
 275+ }
 276+ if ( $count == count( $this->authors ) && $count != 1 ) {
 277+ $authors .= wfMsg( 'word-separator' ) . wfMsg( 'blog-and' ) .
 278+ wfMsg( 'word-separator' );
 279+ }
 280+ $authors .= "<a href=\"{$userTitle->escapeFullURL()}\">{$author['user_name']}</a>";
 281+ }
 282+
 283+ $output .= $authors;
 284+
 285+ $output .= '</div>';
 286+
 287+ $edit_text = '';
 288+ if( $create_date != $edit_date ) {
 289+ $edit_text = ', ' . wfMsg( 'blog-last-edited', $edit_date );
 290+ }
 291+ $output .= "\n" . '<div class="blog-byline-last-edited">' .
 292+ wfMsg( 'blog-created', $create_date ) . " {$edit_text}</div>";
 293+
 294+ return $output;
 295+ }
 296+
 297+ function displayMultipleAuthorsMessage() {
 298+ $count = 0;
 299+
 300+ $authors = '';
 301+ foreach( $this->authors as $author ) {
 302+ $count++;
 303+ $userTitle = Title::makeTitle( NS_USER, $author['user_name'] );
 304+ if ( $authors && count( $this->authors ) > 2 ) {
 305+ $authors .= ', ';
 306+ }
 307+ if ( $count == count( $this->authors ) ) {
 308+ $authors .= wfMsg( 'word-separator' ) . wfMsg( 'blog-and' ) .
 309+ wfMsg( 'word-separator' );
 310+ }
 311+ $authors .= "<a href=\"{$userTitle->escapeFullURL()}\">{$author['user_name']}</a>";
 312+ }
 313+
 314+ $output = '<div class="multiple-authors-message">' .
 315+ wfMsg( 'blog-multiple-authors', $authors ) .
 316+ '</div>';
 317+
 318+ return $output;
 319+ }
 320+
 321+ function displayAuthorBox( $author_index ) {
 322+ global $wgOut, $wgBlogPageDisplay;
 323+
 324+ if ( $wgBlogPageDisplay['author'] == false ) {
 325+ return '';
 326+ }
 327+
 328+ $author_user_name = $author_user_id = '';
 329+ if (
 330+ isset( $this->authors[$author_index] ) &&
 331+ isset( $this->authors[$author_index]['user_name'] )
 332+ )
 333+ {
 334+ $author_user_name = $this->authors[$author_index]['user_name'];
 335+ }
 336+ if (
 337+ isset( $this->authors[$author_index] ) &&
 338+ isset( $this->authors[$author_index]['user_id'] )
 339+ )
 340+ {
 341+ $author_user_id = $this->authors[$author_index]['user_id'];
 342+ }
 343+
 344+ if( empty( $author_user_id ) ) {
 345+ return '';
 346+ }
 347+
 348+ $authorTitle = Title::makeTitle( NS_USER, $author_user_name );
 349+
 350+ $profile = new UserProfile( $author_user_name );
 351+ $profileData = $profile->getProfile();
 352+
 353+ $avatar = new wAvatar( $author_user_id, 'm' );
 354+
 355+ $articles = $this->getAuthorArticles( $author_index );
 356+ $cssFix = '';
 357+ if( !$articles ) {
 358+ $cssFix = ' author-container-fix';
 359+ }
 360+ $output = "\t\t\t\t\t<div class=\"author-container$cssFix\">
 361+ <div class=\"author-info\">
 362+ <a href=\"" . $authorTitle->escapeFullURL() . "\" rel=\"nofollow\">
 363+ {$avatar->getAvatarURL()}
 364+ </a>
 365+ <div class=\"author-title\">
 366+ <a href=\"" . $authorTitle->escapeFullURL() .
 367+ '" rel="nofollow">' .
 368+ wordwrap( $author_user_name, 12, "<br />\n", true ) .
 369+ '</a>
 370+ </div>';
 371+ // If the user has supplied some information about themselves on their
 372+ // social profile, show that data here.
 373+ if( $profileData['about'] ) {
 374+ $output .= $wgOut->parse( $profileData['about'], false );
 375+ }
 376+ $output .= "\n\t\t\t\t\t\t</div><!-- .author-info -->
 377+ <div class=\"cleared\"></div>
 378+ </div><!-- .author-container -->
 379+ {$this->getAuthorArticles( $author_index )}";
 380+
 381+ return $output;
 382+ }
 383+
 384+ function getAuthorArticles( $author_index ) {
 385+ global $wgTitle, $wgOut, $wgBlogPageDisplay, $wgMemc;
 386+
 387+ if ( $wgBlogPageDisplay['author_articles'] == false ) {
 388+ return '';
 389+ }
 390+
 391+ $user_name = $this->authors[$author_index]['user_name'];
 392+ $user_id = $this->authors[$author_index]['user_id'];
 393+ $blogCat = wfMsgForContent( 'blog-category' );
 394+
 395+ $archiveLink = Title::makeTitle(
 396+ NS_CATEGORY,
 397+ wfMsg( 'blog-by-user-category', $blogCat ) . " {$user_name}"
 398+ );
 399+
 400+ $articles = array();
 401+
 402+ // Try cache first
 403+ $key = wfMemcKey( 'blog', 'author', 'articles', $user_id );
 404+ $data = $wgMemc->get( $key );
 405+
 406+ if ( $data != '' ) {
 407+ wfDebugLog( 'BlogPage', "Got blog author articles for user {$user_name} from cache" );
 408+ $articles = $data;
 409+ } else {
 410+ wfDebugLog( 'BlogPage', "Got blog author articles for user {$user_name} from DB" );
 411+ $dbr = wfGetDB( DB_SLAVE );
 412+ $categoryTitle = Title::newFromText(
 413+ wfMsg( 'blog-by-user-category', $blogCat ) . " {$user_name}"
 414+ );
 415+ $res = $dbr->select(
 416+ array( 'page', 'categorylinks'),
 417+ array( 'DISTINCT(page_id) AS page_id', 'page_title' ),
 418+ /* WHERE */array(
 419+ 'cl_to' => array( $categoryTitle->getDBkey() ),
 420+ 'page_namespace' => NS_BLOG
 421+ ),
 422+ __METHOD__,
 423+ array(
 424+ 'ORDER BY' => 'page_id DESC',
 425+ 'LIMIT' => 4
 426+ ),
 427+ array(
 428+ 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' )
 429+ )
 430+ );
 431+
 432+ $array_count = 0;
 433+
 434+ foreach( $res as $row ) {
 435+ if ( $row->page_id != $wgTitle->getArticleID() && $array_count < 3 ) {
 436+ $articles[] = array(
 437+ 'page_title' => $row->page_title,
 438+ 'page_id' => $row->page_id
 439+ );
 440+
 441+ $array_count++;
 442+ }
 443+ }
 444+
 445+ // Cache for half an hour
 446+ $wgMemc->set( $key, $articles, 60 * 30 );
 447+ }
 448+
 449+ $output = '';
 450+ if ( count( $articles ) > 0 ) {
 451+ $css_fix = '';
 452+
 453+ if(
 454+ count( $this->getVotersList() ) == 0 &&
 455+ count( $this->getEditorsList() ) == 0
 456+ )
 457+ {
 458+ $css_fix = ' more-container-fix';
 459+ }
 460+
 461+ $output .= "<div class=\"more-container{$css_fix}\">
 462+ <h3>" . wfMsg( 'blog-author-more-by', $user_name ) . '</h3>';
 463+
 464+ $x = 1;
 465+
 466+ foreach ( $articles as $article ) {
 467+ $articleTitle = Title::makeTitle( NS_BLOG, $article['page_title'] );
 468+
 469+ $output .= '<div class="author-article-item">
 470+ <a href="' . $articleTitle->escapeFullURL() . "\">{$articleTitle->getText()}</a>
 471+ <div class=\"author-item-small\">" .
 472+ wfMsgExt(
 473+ 'blog-author-votes',
 474+ 'parsemag',
 475+ BlogPage::getVotesForPage( $article['page_id'] )
 476+ ) . ', ' .
 477+ wfMsgExt(
 478+ 'blog-author-comments',
 479+ 'parsemag',
 480+ BlogPage::getCommentsForPage( $article['page_id'] )
 481+ ) .
 482+ '</div>
 483+ </div>';
 484+
 485+ $x++;
 486+ }
 487+
 488+ $output .= '<div class="author-archive-link">
 489+ <a href="' . $archiveLink->escapeFullURL() . '">' .
 490+ wfMsg( 'blog-view-archive-link' ) .
 491+ '</a>
 492+ </div>
 493+ </div>';
 494+ }
 495+
 496+ return $output;
 497+ }
 498+
 499+ /**
 500+ * Get the eight newest editors for the current blog post from the revision
 501+ * table.
 502+ *
 503+ * @return Array: array containing each editors' user ID and user name
 504+ */
 505+ function getEditorsList() {
 506+ global $wgMemc, $wgTitle;
 507+
 508+ $pageTitleId = $wgTitle->getArticleID();
 509+
 510+ $key = wfMemcKey( 'recenteditors', 'list', $pageTitleId );
 511+ $data = $wgMemc->get( $key );
 512+ $editors = array();
 513+
 514+ if( !$data ) {
 515+ wfDebugLog( 'BlogPage', "Loading recent editors for page {$pageTitleId} from DB" );
 516+ $dbr = wfGetDB( DB_SLAVE );
 517+
 518+ $where = array(
 519+ 'rev_page' => $pageTitleId,
 520+ 'rev_user <> 0', // exclude anonymous editors
 521+ "rev_user_text <> 'MediaWiki default'", // exclude MW default
 522+ );
 523+
 524+ // Get authors and exclude them
 525+ foreach( $this->authors as $author ) {
 526+ $where[] = 'rev_user_text <> \'' . $author['user_name'] . '\'';
 527+ }
 528+
 529+ $res = $dbr->select(
 530+ 'revision',
 531+ array( 'DISTINCT rev_user', 'rev_user_text' ),
 532+ $where,
 533+ __METHOD__,
 534+ array( 'ORDER BY' => 'rev_user_text ASC', 'LIMIT' => 8 )
 535+ );
 536+
 537+ foreach( $res as $row ) {
 538+ $editors[] = array(
 539+ 'user_id' => $row->rev_user,
 540+ 'user_name' => $row->rev_user_text
 541+ );
 542+ }
 543+
 544+ // Store in memcached for five minutes
 545+ $wgMemc->set( $key, $editors, 60 * 5 );
 546+ } else {
 547+ wfDebugLog( 'BlogPage', "Loading recent editors for page {$pageTitleId} from cache" );
 548+ $editors = $data;
 549+ }
 550+
 551+ return $editors;
 552+ }
 553+
 554+ /**
 555+ * Get the avatars of the people who recently edited this blog post, if
 556+ * this feature is enabled in BlogPage config.
 557+ *
 558+ * @return String: HTML or nothing
 559+ */
 560+ function recentEditors() {
 561+ global $wgUploadPath, $wgBlogPageDisplay;
 562+
 563+ if ( $wgBlogPageDisplay['recent_editors'] == false ) {
 564+ return '';
 565+ }
 566+
 567+ $editors = $this->getEditorsList();
 568+
 569+ $output = '';
 570+
 571+ if ( count( $editors ) > 0 ) {
 572+ $output .= '<div class="recent-container">
 573+ <h2>' . wfMsg( 'blog-recent-editors' ) . '</h2>
 574+ <div>' . wfMsg( 'blog-recent-editors-message' ) . '</div>';
 575+
 576+ foreach( $editors as $editor ) {
 577+ $avatar = new wAvatar( $editor['user_id'], 'm' );
 578+ $userTitle = Title::makeTitle( NS_USER, $editor['user_name'] );
 579+
 580+ $output .= '<a href="' . $userTitle->escapeFullURL() .
 581+ "\"><img src=\"{$wgUploadPath}/avatars/{$avatar->getAvatarImage()}\" alt=\"" .
 582+ $userTitle->getText() . '" border="0" /></a>';
 583+ }
 584+
 585+ $output .= '</div>';
 586+ }
 587+
 588+ return $output;
 589+ }
 590+
 591+ /**
 592+ * Get the eight newest voters for the current blog post from VoteNY's
 593+ * Vote table.
 594+ *
 595+ * @return Array: array containing each voters' user ID and user name
 596+ */
 597+ function getVotersList() {
 598+ global $wgMemc, $wgTitle;
 599+
 600+ // Gets the page ID for the query
 601+ $pageTitleId = $wgTitle->getArticleID();
 602+
 603+ $key = wfMemcKey( 'recentvoters', 'list', $pageTitleId );
 604+ $data = $wgMemc->get( $key );
 605+
 606+ $voters = array();
 607+ if( !$data ) {
 608+ wfDebugLog( 'BlogPage', "Loading recent voters for page {$pageTitleId} from DB" );
 609+ $dbr = wfGetDB( DB_SLAVE );
 610+
 611+ $where = array(
 612+ 'vote_page_id' => $pageTitleId,
 613+ 'vote_user_id <> 0'
 614+ );
 615+
 616+ // Exclude the authors of the blog post from the list of recent
 617+ // voters
 618+ foreach( $this->authors as $author ) {
 619+ $where[] = 'username <> \'' . $author['user_name'] . '\'';
 620+ }
 621+
 622+ $res = $dbr->select(
 623+ 'Vote',
 624+ array( 'DISTINCT username', 'vote_user_id', 'vote_page_id' ),
 625+ $where,
 626+ __METHOD__,
 627+ array( 'ORDER BY' => 'vote_id DESC', 'LIMIT' => 8 )
 628+ );
 629+
 630+ foreach ( $res as $row ) {
 631+ $voters[] = array(
 632+ 'user_id' => $row->vote_user_id,
 633+ 'user_name' => $row->username
 634+ );
 635+ }
 636+
 637+ $wgMemc->set( $key, $voters, 60 * 5 );
 638+ } else {
 639+ wfDebugLog( 'BlogPage', "Loading recent voters for page {$pageTitleId} from cache" );
 640+ $voters = $data;
 641+ }
 642+
 643+ return $voters;
 644+ }
 645+
 646+ /**
 647+ * Get the avatars of the people who recently voted for this blog post, if
 648+ * this feature is enabled in BlogPage config.
 649+ *
 650+ * @return String: HTML or nothing
 651+ */
 652+ function recentVoters() {
 653+ global $wgBlogPageDisplay;
 654+
 655+ if ( $wgBlogPageDisplay['recent_voters'] == false ) {
 656+ return '';
 657+ }
 658+
 659+ $voters = $this->getVotersList();
 660+
 661+ $output = '';
 662+
 663+ if( count( $voters ) > 0 ) {
 664+ $output .= '<div class="recent-container bottom-fix">
 665+ <h2>' . wfMsg( 'blog-recent-voters' ) . '</h2>
 666+ <div>' . wfMsg( 'blog-recent-voters-message' ) . '</div>';
 667+
 668+ foreach( $voters as $voter ) {
 669+ $userTitle = Title::makeTitle( NS_USER, $voter['user_name'] );
 670+ $avatar = new wAvatar( $voter['user_id'], 'm' );
 671+
 672+ $output .= '<a href="' . $userTitle->escapeFullURL() .
 673+ "\">{$avatar->getAvatarURL()}</a>";
 674+ }
 675+
 676+ $output .= '</div>';
 677+ }
 678+
 679+ return $output;
 680+ }
 681+
 682+ /**
 683+ * Get the embed widget, if this feature is enabled in BlogPage config.
 684+ *
 685+ * @return String: HTML or nothing
 686+ */
 687+ function embedWidget() {
 688+ global $wgTitle, $wgBlogPageDisplay, $wgServer, $wgScriptPath;
 689+
 690+ // Not enabled? ContentWidget not available?
 691+ if (
 692+ $wgBlogPageDisplay['embed_widget'] == false ||
 693+ !is_dir( dirname( __FILE__ ) . '/../extensions/ContentWidget' )
 694+ )
 695+ {
 696+ return '';
 697+ }
 698+
 699+ $title = Title::makeTitle( $wgTitle->getNamespace(), $wgTitle->getText() );
 700+
 701+ $output = '';
 702+ $output .= '<div class="recent-container bottom-fix"><h2>' .
 703+ wfMsg( 'blog-embed-title' ) . '</h2>';
 704+ $output .= '<div class="blog-widget-embed">';
 705+ $output .= "<p><input type='text' size='20' onclick='this.select();' value='" .
 706+ '<object width="300" height="450" id="content_widget" align="middle"> <param name="movie" value="content_widget.swf" /><embed src="' .
 707+ $wgServer . $wgScriptPath . '/extensions/ContentWidget/widget.swf?page=' .
 708+ urlencode( $title->getFullText() ) . '" quality="high" bgcolor="#ffffff" width="300" height="450" name="content_widget"type="application/x-shockwave-flash" /> </object>' . "' /></p></div>";
 709+ $output .= '</div>';
 710+
 711+ return $output;
 712+ }
 713+
 714+ /**
 715+ * Get an ad unit for the left side, if this feature is enabled in BlogPage
 716+ * config.
 717+ *
 718+ * @return String: HTML or nothing
 719+ */
 720+ function leftAdUnit() {
 721+ global $wgBlogPageDisplay;
 722+
 723+ if ( $wgBlogPageDisplay['left_ad'] == false ) {
 724+ return '';
 725+ }
 726+
 727+ $output = '<div class="article-ad">
 728+ <!-- BlogPage ad temporarily disabled -->
 729+ </div>';
 730+
 731+ return $output;
 732+ }
 733+
 734+ /**
 735+ * Get some random news items from MediaWiki:Inthenews, if this feature is
 736+ * enabled in BlogPage config and that interface message has some content.
 737+ *
 738+ * @return String: HTML or nothing
 739+ */
 740+ function getInTheNews() {
 741+ global $wgBlogPageDisplay, $wgMemc, $wgOut;
 742+
 743+ if ( $wgBlogPageDisplay['in_the_news'] == false ) {
 744+ return '';
 745+ }
 746+
 747+ $output = '';
 748+ $message = wfMsgForContent( 'inthenews' );
 749+ if ( !wfEmptyMsg( 'inthenews', $message ) ) {
 750+ $newsArray = explode( "\n\n", $message );
 751+ $newsItem = $newsArray[array_rand( $newsArray )];
 752+ $output = '<div class="blog-container">
 753+ <h2>' . wfMsg( 'blog-in-the-news' ) . '</h2>
 754+ <div>' . $wgOut->parse( $newsItem, false ) . '</div>
 755+ </div>';
 756+ }
 757+
 758+ return $output;
 759+ }
 760+
 761+ /**
 762+ * Get the five most popular blog articles, if this feature is enabled in
 763+ * BlogPage config.
 764+ *
 765+ * @return String: HTML or nothing
 766+ */
 767+ function getPopularArticles() {
 768+ global $wgMemc, $wgBlogPageDisplay;
 769+
 770+ if ( $wgBlogPageDisplay['popular_articles'] == false ) {
 771+ return '';
 772+ }
 773+
 774+ // Try cache first
 775+ $key = wfMemcKey( 'blog', 'popular', 'five' );
 776+ $data = $wgMemc->get( $key );
 777+
 778+ if( $data != '' ) {
 779+ wfDebugLog( 'BlogPage', 'Got popular articles from cache' );
 780+ $popularBlogPosts = $data;
 781+ } else {
 782+ wfDebugLog( 'BlogPage', 'Got popular articles from DB' );
 783+ $blogCat = wfMsgForContent( 'blog-category' );
 784+ $dbr = wfGetDB( DB_SLAVE );
 785+ // Code sporked from Rob Church's NewestPages extension
 786+ // @todo FIXME: adding categorylinks table and that one where
 787+ // clause causes an error about "unknown column 'page_id' on ON
 788+ // clause"
 789+ $commentsTable = $dbr->tableName( 'Comments' );
 790+ $voteTable = $dbr->tableName( 'Vote' );
 791+ $res = $dbr->select(
 792+ array( 'page', /*'categorylinks',*/ 'Comments', 'Vote' ),
 793+ array(
 794+ 'DISTINCT page_id', 'page_namespace', 'page_is_redirect',
 795+ 'page_title',
 796+ ),
 797+ array(
 798+ 'page_namespace' => NS_BLOG,
 799+ 'page_is_redirect' => 0,
 800+ 'page_id = Comment_Page_ID',
 801+ 'page_id = vote_page_id',
 802+ #'cl_to ' . $dbr->buildLike( /*$dbr->anyString(), */$blogCat, $dbr->anyString() ),
 803+ // If you can figure out how to do this without a subquery,
 804+ // please let me know. Until that...
 805+ "((SELECT COUNT(*) FROM $voteTable WHERE vote_page_id = page_id) >= 5 OR
 806+ (SELECT COUNT(*) FROM $commentsTable WHERE Comment_Page_ID = page_id) >= 5)",
 807+ ),
 808+ __METHOD__,
 809+ array(
 810+ 'ORDER BY' => 'page_id DESC',
 811+ 'LIMIT' => 10
 812+ ),
 813+ array(
 814+ 'Comments' => array( 'INNER JOIN', 'page_id = Comment_Page_ID' ),
 815+ 'Vote' => array( 'INNER JOIN', 'page_id = vote_page_id' )
 816+ )
 817+ );
 818+
 819+ $popularBlogPosts = array();
 820+ foreach ( $res as $row ) {
 821+ $popularBlogPosts[] = array(
 822+ 'title' => $row->page_title,
 823+ 'id' => $row->page_id
 824+ );
 825+ }
 826+
 827+ // Cache in memcached for 15 minutes
 828+ $wgMemc->set( $key, $popularBlogPosts, 60 * 15 );
 829+ }
 830+
 831+ $html = '<div class="listpages-container">';
 832+ foreach( $popularBlogPosts as $popularBlogPost ) {
 833+ $titleObj = Title::makeTitle( NS_BLOG, $popularBlogPost['title'] );
 834+ $html .= '<div class="listpages-item">
 835+ <a href="' . $titleObj->escapeFullURL() . '">' .
 836+ $titleObj->getText() .
 837+ '</a>
 838+ </div>
 839+ <div class="cleared"></div>';
 840+ }
 841+ $html .= '</div>'; // .listpages-container
 842+
 843+ $output = '<div class="blog-container">
 844+ <h2>' . wfMsg( 'blog-popular-articles' ) . '</h2>
 845+ <div>' . $html . '</div>
 846+ </div>';
 847+
 848+ return $output;
 849+ }
 850+
 851+ /**
 852+ * Get the newest blog articles, if this feature is enabled in BlogPage
 853+ * config.
 854+ *
 855+ * @return String: HTML or nothing
 856+ */
 857+ function getNewArticles() {
 858+ global $wgOut, $wgMemc, $wgBlogPageDisplay;
 859+
 860+ if ( $wgBlogPageDisplay['new_articles'] == false ) {
 861+ return '';
 862+ }
 863+
 864+ // Try cache first
 865+ $key = wfMemcKey( 'blog', 'new', 'five' );
 866+ $data = $wgMemc->get( $key );
 867+
 868+ if( $data != '' ) {
 869+ wfDebugLog( 'BlogPage', 'Got new articles from cache' );
 870+ $newBlogPosts = $data;
 871+ } else {
 872+ wfDebugLog( 'BlogPage', 'Got new articles from DB' );
 873+ // We could do complicated LIKE stuff with the categorylinks table,
 874+ // but I think we can safely assume that stuff in the NS_BLOG NS
 875+ // is blog-related :)
 876+ //$blogCat = wfMsgForContent( 'blog-category' );
 877+ $dbr = wfGetDB( DB_SLAVE );
 878+ // Code sporked from Rob Church's NewestPages extension
 879+ $res = $dbr->select(
 880+ 'page',
 881+ array( 'page_namespace', 'page_title', 'page_is_redirect' ),
 882+ array( 'page_namespace' => NS_BLOG, 'page_is_redirect' => 0 ),
 883+ __METHOD__,
 884+ array( 'ORDER BY' => 'page_id DESC', 'LIMIT' => 5 )
 885+ );
 886+
 887+ $newBlogPosts = array();
 888+ foreach ( $res as $row ) {
 889+ $newBlogPosts[] = array(
 890+ 'title' => $row->page_title,
 891+ );
 892+ }
 893+
 894+ // Cache in memcached for 15 minutes
 895+ $wgMemc->set( $key, $newBlogPosts, 60 * 15 );
 896+ }
 897+
 898+ $html = '<div class="listpages-container">';
 899+ foreach( $newBlogPosts as $newBlogPost ) {
 900+ $titleObj = Title::makeTitle( NS_BLOG, $newBlogPost['title'] );
 901+ $html .= '<div class="listpages-item">
 902+ <a href="' . $titleObj->escapeFullURL() . '">' .
 903+ $titleObj->getText() .
 904+ '</a>
 905+ </div>
 906+ <div class="cleared"></div>';
 907+ }
 908+ $html .= '</div>'; // .listpages-container
 909+
 910+ $output = '<div class="blog-container bottom-fix">
 911+ <h2>' . wfMsg( 'blog-new-articles' ) . '</h2>
 912+ <div>' . $html . '</div>
 913+ </div>';
 914+
 915+ return $output;
 916+ }
 917+
 918+ /**
 919+ * Get a random casual game, if this feature is enabled in BlogPage config
 920+ * and the RandomGameUnit extension is installed.
 921+ *
 922+ * @return String: HTML or nothing
 923+ */
 924+ function getRandomCasualGame() {
 925+ global $wgBlogPageDisplay;
 926+
 927+ if (
 928+ $wgBlogPageDisplay['games'] == false ||
 929+ !function_exists( 'wfGetRandomGameUnit' )
 930+ )
 931+ {
 932+ return '';
 933+ }
 934+
 935+ return wfGetRandomGameUnit();
 936+ }
 937+
 938+ /**
 939+ * Get comments of the day, if this feature is enabled in BlogPage config.
 940+ * Requires the Comments extension.
 941+ *
 942+ * @return String: HTML or nothing
 943+ */
 944+ function getCommentsOfTheDay() {
 945+ global $wgBlogPageDisplay, $wgMemc, $wgLang;
 946+
 947+ if ( $wgBlogPageDisplay['comments_of_day'] == false ) {
 948+ return '';
 949+ }
 950+
 951+ $comments = array();
 952+
 953+ // Try cache first
 954+ $key = wfMemcKey( 'comments', 'plus', '24hours' );
 955+ $data = $wgMemc->get( $key );
 956+
 957+ if( $data != '' ) {
 958+ wfDebugLog( 'BlogPage', 'Got comments of the day from cache' );
 959+ $comments = $data;
 960+ } else {
 961+ wfDebugLog( 'BlogPage', 'Got comments of the day from DB' );
 962+ $dbr = wfGetDB( DB_SLAVE );
 963+ $res = $dbr->select(
 964+ array( 'Comments', 'page' ),
 965+ array(
 966+ 'Comment_Username', 'comment_ip', 'comment_text',
 967+ 'comment_date', 'Comment_user_id', 'CommentID',
 968+ 'IFNULL(Comment_Plus_Count - Comment_Minus_Count,0) AS Comment_Score',
 969+ 'Comment_Plus_Count AS CommentVotePlus',
 970+ 'Comment_Minus_Count AS CommentVoteMinus',
 971+ 'Comment_Parent_ID', 'page_title', 'page_namespace'
 972+ ),
 973+ array(
 974+ 'comment_page_id = page_id',
 975+ 'UNIX_TIMESTAMP(comment_date) > ' . ( time() - ( 60 * 60 * 24 ) ),
 976+ 'page_namespace' => NS_BLOG
 977+ ),
 978+ __METHOD__,
 979+ array( 'ORDER BY' => 'Comment_Plus_Count DESC', 'LIMIT' => 5 )
 980+ );
 981+
 982+ foreach( $res as $row ) {
 983+ $comments[] = array(
 984+ 'user_name' => $row->Comment_Username,
 985+ 'user_id' => $row->Comment_user_id,
 986+ 'title' => $row->page_title,
 987+ 'namespace' => $row->page_namespace,
 988+ 'comment_id' => $row->CommentID,
 989+ 'plus_count' => $row->CommentVotePlus,
 990+ 'comment_text' => $row->comment_text
 991+ );
 992+ }
 993+
 994+ $wgMemc->set( $key, $comments, 60 * 15 );
 995+ }
 996+
 997+ $output = '';
 998+
 999+ foreach( $comments as $comment ) {
 1000+ $page_title = Title::makeTitle( $comment['namespace'], $comment['title'] );
 1001+
 1002+ if( $comment['user_id'] != 0 ) {
 1003+ $commentPosterDisplay = $comment['user_name'];
 1004+ } else {
 1005+ $commentPosterDisplay = wfMsg( 'blog-anonymous-name' );
 1006+ }
 1007+
 1008+ $comment['comment_text'] = strip_tags( $comment['comment_text'] );
 1009+ $comment_text = $wgLang->truncate(
 1010+ $comment['comment_text'],
 1011+ ( 70 - strlen( $commentPosterDisplay ) )
 1012+ );
 1013+
 1014+ $output .= '<div class="cod-item">';
 1015+ $output .= "<span class=\"cod-score\">{$comment['plus_count']}</span> ";
 1016+ $output .= " <span class=\"cod-comment\"><a href=\"{$page_title->escapeFullURL()}#comment-{$comment['comment_id']}\" title=\"{$page_title->getText()}\" >{$comment_text}</a></span>";
 1017+ $output .= '</div>';
 1018+ }
 1019+
 1020+ if ( count( $comments ) > 0 ) {
 1021+ $output = '<div class="blog-container">
 1022+ <h2>' . wfMsg( 'blog-comments-of-day' ) . '</h2>' .
 1023+ $output .
 1024+ '</div>';
 1025+ }
 1026+
 1027+ return $output;
 1028+ }
 1029+
 1030+ /**
 1031+ * Get the amount (COUNT(*)) of comments for the given page, identified via
 1032+ * its ID and cache this info in memcached for 15 minutes.
 1033+ *
 1034+ * @param $id Integer: page ID
 1035+ * @return Integer: amount of comments
 1036+ */
 1037+ public static function getCommentsForPage( $id ) {
 1038+ global $wgMemc;
 1039+
 1040+ // Try cache first
 1041+ $key = wfMemcKey( 'blog', 'comments', 'count' );
 1042+ $data = $wgMemc->get( $key );
 1043+
 1044+ if( $data != '' ) {
 1045+ wfDebugLog( 'BlogPage', "Got comments count for the page with ID {$id} from cache" );
 1046+ $commentCount = $data;
 1047+ } else {
 1048+ wfDebugLog( 'BlogPage', "Got comments count for the page with ID {$id} from DB" );
 1049+ $dbr = wfGetDB( DB_SLAVE );
 1050+ $commentCount = (int)$dbr->selectField(
 1051+ 'Comments',
 1052+ 'COUNT(*) AS count',
 1053+ array( 'Comment_Page_ID' => intval( $id ) ),
 1054+ __METHOD__
 1055+ );
 1056+ // Store in memcached for 15 minutes
 1057+ $wgMemc->set( $key, $commentCount, 60 * 15 );
 1058+ }
 1059+
 1060+ return $commentCount;
 1061+ }
 1062+
 1063+ /**
 1064+ * Get the amount (COUNT(*)) of votes for the given page, identified via
 1065+ * its ID and cache this info in memcached for 15 minutes.
 1066+ *
 1067+ * @param $id Integer: page ID
 1068+ * @return Integer: amount of votes
 1069+ */
 1070+ public static function getVotesForPage( $id ) {
 1071+ global $wgMemc;
 1072+
 1073+ // Try cache first
 1074+ $key = wfMemcKey( 'blog', 'vote', 'count' );
 1075+ $data = $wgMemc->get( $key );
 1076+
 1077+ if( $data != '' ) {
 1078+ wfDebugLog( 'BlogPage', "Got vote count for the page with ID {$id} from cache" );
 1079+ $voteCount = $data;
 1080+ } else {
 1081+ wfDebugLog( 'BlogPage', "Got vote count for the page with ID {$id} from DB" );
 1082+ $dbr = wfGetDB( DB_SLAVE );
 1083+ $voteCount = (int)$dbr->selectField(
 1084+ 'Vote',
 1085+ 'COUNT(*) AS count',
 1086+ array( 'vote_page_id' => intval( $id ) ),
 1087+ __METHOD__
 1088+ );
 1089+ // Store in memcached for 15 minutes
 1090+ $wgMemc->set( $key, $voteCount, 60 * 15 );
 1091+ }
 1092+
 1093+ return $voteCount;
 1094+ }
 1095+
 1096+ /**
 1097+ * Get the first $maxChars characters of a page.
 1098+ *
 1099+ * @param $pageTitle String: page title
 1100+ * @param $namespace Integer: namespace where the page is in
 1101+ * @param $maxChars Integer: get the first this many characters of the page
 1102+ * @param $fontSize String: small, medium or large
 1103+ * @return String: first $maxChars characters from the page
 1104+ */
 1105+ public static function getBlurb( $pageTitle, $namespace, $maxChars, $fontSize = 'small' ) {
 1106+ global $wgTitle, $wgOut, $wgContLang;
 1107+
 1108+ // Get raw text
 1109+ $title = Title::makeTitle( $namespace, $pageTitle );
 1110+ $article = new Article( $title );
 1111+ $text = $article->getContent();
 1112+
 1113+ // Remove some problematic characters
 1114+ $text = str_replace( '* ', '', $text );
 1115+ $text = str_replace( '===', '', $text );
 1116+ $text = str_replace( '==', '', $text );
 1117+ $text = str_replace( '{{Comments}}', '', $text ); // Template:Comments
 1118+ $text = preg_replace( '@<youtube[^>]*?>.*?</youtube>@si', '', $text ); // <youtube> tags (provided by YouTube extension)
 1119+ $text = preg_replace( '@<video[^>]*?>.*?</video>@si', '', $text ); // <video> tags (provided by Video extension)
 1120+ $text = preg_replace( '@<comments[^>]*?>.*?</comments>@si', '', $text ); // <comments> tags (provided by Comments extension)
 1121+ $text = preg_replace( '@<vote[^>]*?>.*?</vote>@si', '', $text ); // <vote> tags (provided by Vote extension)
 1122+ if ( class_exists( 'Video' ) ) {
 1123+ $videoNS = $wgContLang->getNsText( NS_VIDEO );
 1124+ if ( $videoNS === false ) {
 1125+ $videoNS = 'Video';
 1126+ }
 1127+ // [[Video:]] links (provided by Video extension)
 1128+ $text = preg_replace( "@\[\[{$videoNS}:[^\]]*?].*?\]@si", '', $text );
 1129+ }
 1130+ $localizedCategoryNS = $wgContLang->getNsText( NS_CATEGORY );
 1131+ $text = preg_replace( "@\[\[(?:(c|C)ategory|{$localizedCategoryNS}):[^\]]*?].*?\]@si", '', $text ); // categories
 1132+ //$text = preg_replace( "@\[\[{$localizedCategoryNS}:[^\]]*?].*?\]@si", '', $text ); // original version of the above line
 1133+
 1134+ // Start looking at text after content, and force no Table of Contents
 1135+ $pos = strpos( $text, '<!--start text-->' );
 1136+ if( $pos !== false ) {
 1137+ $text = substr( $text, $pos );
 1138+ }
 1139+
 1140+ $text = '__NOTOC__ ' . $text;
 1141+
 1142+ // Run text through parser
 1143+ $blurbParser = new Parser();
 1144+ $blurbText = $blurbParser->parse( $text, $wgTitle, $wgOut->parserOptions(), true );
 1145+ $blurbText = strip_tags( $blurbText->getText() );
 1146+
 1147+ $blurbText = preg_replace( '/&lt;comments&gt;&lt;\/comments&gt;/i', '', $blurbText );
 1148+ $blurbText = preg_replace( '/&lt;vote&gt;&lt;\/vote&gt;/i', '', $blurbText );
 1149+
 1150+ //$blurbText = $text;
 1151+ $pos = strpos( $blurbText, '[' );
 1152+ if( $pos !== false ) {
 1153+ $blurbText = substr( $blurbText, 0, $pos );
 1154+ }
 1155+
 1156+ // Take first N characters, and then make sure it ends on last full word
 1157+ $max = 300;
 1158+ if( strlen( $blurbText ) > $max ) {
 1159+ $blurbText = strrev( strstr( strrev( substr( $blurbText, 0, $max ) ), ' ' ) );
 1160+ }
 1161+
 1162+ // Prepare blurb font size
 1163+ $blurbFont = '<span class="listpages-blurb-size-';
 1164+ if ( $fontSize == 'small' ) {
 1165+ $blurbFont .= 'small';
 1166+ } elseif ( $fontSize == 'medium' ) {
 1167+ $blurbFont .= 'medium';
 1168+ } elseif ( $fontSize == 'large' ) {
 1169+ $blurbFont .= 'large';
 1170+ }
 1171+ $blurbFont .= '">';
 1172+
 1173+ // Fix multiple whitespace, returns etc
 1174+ $blurbText = trim( $blurbText ); // remove trailing spaces
 1175+ $blurbText = preg_replace( '/\s(?=\s)/', '', $blurbText ); // remove double whitespace
 1176+ $blurbText = preg_replace( '/[\n\r\t]/', ' ', $blurbText ); // replace any non-space whitespace with a space
 1177+
 1178+ return $blurbFont . $blurbText. '. . . <a href="' .
 1179+ $title->escapeFullURL() . '">' . wfMsg( 'blog-more' ) .
 1180+ '</a></span>';
 1181+ }
 1182+
 1183+ /**
 1184+ * Get the image associated with the given page (via the page's ID).
 1185+ *
 1186+ * @param $pageId Integer: page ID number
 1187+ * @return String: file name or nothing
 1188+ */
 1189+ public static function getPageImage( $pageId ) {
 1190+ global $wgMemc;
 1191+
 1192+ $key = wfMemcKey( 'blog', 'page', 'image', $pageId );
 1193+ $data = $wgMemc->get( $key );
 1194+
 1195+ if( !$data ) {
 1196+ $dbr = wfGetDB( DB_SLAVE );
 1197+ $il_to = $dbr->selectField(
 1198+ 'imagelinks',
 1199+ array( 'il_to' ),
 1200+ array( 'il_from' => intval( $pageId ) ),
 1201+ __METHOD__
 1202+ );
 1203+ // Cache in memcached for a minute
 1204+ $wgMemc->set( $key, $il_to, 60 );
 1205+ } else {
 1206+ wfDebugLog( 'BlogPage', "Loading image for page {$pageId} from cache\n" );
 1207+ $il_to = $data;
 1208+ }
 1209+
 1210+ return $il_to;
 1211+ }
 1212+
 1213+ /**
 1214+ * Yes, these are those fucking time-related functions once more.
 1215+ * You probably have seen these in UserBoard, Comments...god knows where.
 1216+ * Seriously, this stuff is all over the place.
 1217+ */
 1218+ static function dateDiff( $date1, $date2 ) {
 1219+ $dtDiff = $date1 - $date2;
 1220+
 1221+ $totalDays = intval( $dtDiff / ( 24 * 60 * 60 ) );
 1222+ $totalSecs = $dtDiff - ( $totalDays * 24 * 60 * 60 );
 1223+ $dif['w'] = intval( $totalDays / 7 );
 1224+ $dif['d'] = $totalDays;
 1225+ $dif['h'] = $h = intval( $totalSecs / ( 60 * 60 ) );
 1226+ $dif['m'] = $m = intval( ( $totalSecs - ( $h * 60 * 60 ) ) / 60 );
 1227+ $dif['s'] = $totalSecs - ( $h * 60 * 60 ) - ( $m * 60 );
 1228+
 1229+ return $dif;
 1230+ }
 1231+
 1232+ static function getTimeOffset( $time, $timeabrv, $timename ) {
 1233+ $timeStr = '';
 1234+ if( $time[$timeabrv] > 0 ) {
 1235+ $timeStr = wfMsgExt( "blog-time-{$timename}", 'parsemag', $time[$timeabrv] );
 1236+ }
 1237+ if( $timeStr ) {
 1238+ $timeStr .= ' ';
 1239+ }
 1240+ return $timeStr;
 1241+ }
 1242+
 1243+ static function getTimeAgo( $time ) {
 1244+ $timeArray = self::dateDiff( time(), $time );
 1245+ $timeStr = '';
 1246+ $timeStrD = self::getTimeOffset( $timeArray, 'd', 'days' );
 1247+ $timeStrH = self::getTimeOffset( $timeArray, 'h', 'hours' );
 1248+ $timeStrM = self::getTimeOffset( $timeArray, 'm', 'minutes' );
 1249+ $timeStrS = self::getTimeOffset( $timeArray, 's', 'seconds' );
 1250+ $timeStr = $timeStrD;
 1251+ if( $timeStr < 2 ) {
 1252+ $timeStr .= $timeStrH;
 1253+ $timeStr .= $timeStrM;
 1254+ if( !$timeStr ) {
 1255+ $timeStr .= $timeStrS;
 1256+ }
 1257+ }
 1258+ if( !$timeStr ) {
 1259+ $timeStr = wfMsgExt( 'blog-time-seconds', 'parsemag', 1 );
 1260+ }
 1261+ return $timeStr;
 1262+ }
 1263+}
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/BlogPage.php
___________________________________________________________________
Added: svn:eol-style
11264 + native
Index: trunk/extensions/BlogPage/SpecialArticlesHome.php
@@ -0,0 +1,731 @@
 2+<?php
 3+/**
 4+ * Blogs homepage - blog articles will make it to this page when they receive a
 5+ * certain number of votes and/or unique commentors commenting.
 6+ *
 7+ * In addition to the most popular blog posts, this page will display the
 8+ * newest blog posts, the most commented and most voted blog posts within the
 9+ * past 72 hours.
 10+ *
 11+ * @file
 12+ * @ingroup Extensions
 13+ */
 14+class ArticlesHome extends SpecialPage {
 15+
 16+ /**
 17+ * Constructor -- set up the new special page
 18+ */
 19+ public function __construct() {
 20+ parent::__construct( 'ArticlesHome' );
 21+ }
 22+
 23+ /**
 24+ * Show the new special page
 25+ *
 26+ * @param $type String: what kind of articles to show? Default is 'popular'
 27+ */
 28+ public function execute( $type ) {
 29+ global $wgContLang, $wgOut, $wgScriptPath, $wgSupressPageTitle;
 30+
 31+ $wgSupressPageTitle = true;
 32+
 33+ // Add CSS
 34+ if ( defined( 'MW_SUPPORTS_RESOURCE_MODULES' ) ) {
 35+ $wgOut->addModules( 'ext.blogPage.articlesHome' );
 36+ } else {
 37+ $wgOut->addExtensionStyle( $wgScriptPath . '/extensions/BlogPage/ArticlesHome.css' );
 38+ }
 39+
 40+ if( !$type ) {
 41+ $type = 'popular';
 42+ }
 43+
 44+ // Get the category names for today and the past two days
 45+ $dates_array = $this->getDatesFromElapsedDays( 2 );
 46+ $date_categories = '';
 47+ foreach ( $dates_array as $key => $value ) {
 48+ if( $date_categories ) {
 49+ $date_categories .= ',';
 50+ }
 51+ $date_categories .= $key;
 52+ }
 53+
 54+ // Determine the page title and set it
 55+ if ( $type == 'popular' ) {
 56+ $name = wfMsg( 'ah-popular-articles' );
 57+ $name_right = wfMsg( 'ah-new-articles' );
 58+ } else {
 59+ $name = wfMsg( 'ah-new-articles' );
 60+ $name_right = wfMsg( 'ah-popular-articles' );
 61+ }
 62+
 63+ $wgOut->setPageTitle( $name );
 64+
 65+ $today = $wgContLang->date( wfTimestampNow() );
 66+
 67+ // Start building the HTML output
 68+ $output = '<div class="main-page-left">';
 69+ $output .= '<div class="logged-in-articles">';
 70+ $output .= '<h2>' . $name . '</h2>';
 71+ //$output .= '<h2>' . $name . ' <span class="rss-feed"><a href="http://feeds.feedburner.com/Armchairgm"><img src="http://www.armchairgm.com/images/a/a7/Rss-icon.gif" border="0" alt="RSS" /></a> ' . wfMsg( 'ah-feed-rss' ) . '</span></h2>';
 72+ $output .= '<p class="main-page-sub-links"><a href="' .
 73+ SpecialPage::getTitleFor( 'CreateBlogPost' )->escapeFullURL() . '">' .
 74+ wfMsg( 'ah-write-article' ) . '</a> - <a href="' .
 75+ // original used date( 'F j, Y' ) which returned something like
 76+ // December 5, 2008
 77+ Title::makeTitle( NS_CATEGORY, $today )->escapeFullURL() . '">' .
 78+ wfMsg( 'ah-todays-articles' ) . '</a> - <a href="' .
 79+ Title::newMainPage()->escapeFullURL() . '">' .
 80+ wfMsg( 'mainpage' ) . '</a></p>' . "\n\n";
 81+
 82+ if ( $type == 'popular' ) {
 83+ $output .= $this->getPopularPosts();
 84+ } else {
 85+ $output .= $this->getNewestPosts();
 86+ }
 87+
 88+ $output .= '</div>';
 89+ $output .= '</div>';
 90+ $output .= '<div class="main-page-right">';
 91+
 92+ // Side Articles
 93+ $output .= '<div class="side-articles">';
 94+ $output .= '<h2>' . $name_right . '</h2>';
 95+
 96+ if ( $type == 'popular' ) {
 97+ $output .= $this->displayNewestPages();
 98+ } else {
 99+ $output .= $this->getPopularPostsForRightSide();
 100+ }
 101+
 102+ $output .= '</div>';
 103+
 104+ wfDebugLog( 'BlogPage', 'ArticlesHome: date_categories=' . $date_categories );
 105+
 106+ // Most Votes
 107+ $output .= '<div class="side-articles">';
 108+ $output .= '<h2>' . wfMsg( 'ah-most-votes' ) . '</h2>';
 109+ $output .= $this->displayMostVotedPages( $date_categories );
 110+ $output .= '</div>';
 111+
 112+ // Most Comments
 113+ $output .= '<div class="side-articles">';
 114+ $output .= '<h2>' . wfMsg( 'ah-what-talking-about' ) . '</h2>';
 115+ $output .= $this->displayMostCommentedPages( $date_categories );
 116+ $output .= '</div>';
 117+
 118+ $output .= '</div>';
 119+ $output .= '<div class="cleared"></div>';
 120+
 121+ $wgOut->addHTML( $output );
 122+ }
 123+
 124+ /**
 125+ * @param $numberOfDays Integer: get this many days in addition to today
 126+ * @return Array: array containing today and the past $numberOfDays days in
 127+ * the wiki's content language
 128+ */
 129+ function getDatesFromElapsedDays( $numberOfDays ) {
 130+ global $wgContLang;
 131+ $today = $wgContLang->date( wfTimestampNow() ); // originally date( 'F j, Y', time() )
 132+ $dates[$today] = 1; // Gets today's date string
 133+ for( $x = 1; $x <= $numberOfDays; $x++ ) {
 134+ $timeAgo = time() - ( 60 * 60 * 24 * $x );
 135+ // originally date( 'F j, Y', $timeAgo );
 136+ $dateString = $wgContLang->date( wfTimestamp( TS_MW, $timeAgo ) );
 137+ $dates[$dateString] = 1;
 138+ }
 139+ return $dates;
 140+ }
 141+
 142+ /**
 143+ * Get the 25 most popular blog posts from the database and then cache them
 144+ * in memcached for 15 minutes.
 145+ * The definition of 'popular' is very arbitrary at the moment.
 146+ *
 147+ * @return String: HTML
 148+ */
 149+ public function getPopularPosts() {
 150+ global $wgMemc, $wgScriptPath;
 151+
 152+ // Try cache first
 153+ $key = wfMemcKey( 'blog', 'popular', 'twentyfive' );
 154+ $data = $wgMemc->get( $key );
 155+
 156+ if( $data != '' ) {
 157+ wfDebugLog( 'BlogPage', 'Got popular posts in ArticlesHome from cache' );
 158+ $popularBlogPosts = $data;
 159+ } else {
 160+ wfDebugLog( 'BlogPage', 'Got popular posts in ArticlesHome from DB' );
 161+ $dbr = wfGetDB( DB_SLAVE );
 162+ // Code sporked from Rob Church's NewestPages extension
 163+ $commentsTable = $dbr->tableName( 'Comments' );
 164+ $voteTable = $dbr->tableName( 'Vote' );
 165+ $res = $dbr->select(
 166+ array( 'page', 'Comments', 'Vote' ),
 167+ array(
 168+ 'DISTINCT page_id', 'page_namespace', 'page_title',
 169+ 'page_is_redirect',
 170+ ),
 171+ array(
 172+ 'page_namespace' => NS_BLOG,
 173+ 'page_is_redirect' => 0,
 174+ // If you can figure out how to do this without a subquery,
 175+ // please let me know. Until that...
 176+ "((SELECT COUNT(*) FROM $voteTable WHERE vote_page_id = page_id) >= 5 OR
 177+ (SELECT COUNT(*) FROM $commentsTable WHERE Comment_Page_ID = page_id) >= 5)",
 178+ ),
 179+ __METHOD__,
 180+ array(
 181+ 'ORDER BY' => 'page_id DESC',
 182+ 'LIMIT' => 25
 183+ ),
 184+ array(
 185+ 'Comments' => array( 'INNER JOIN', 'page_id = Comment_Page_ID' ),
 186+ 'Vote' => array( 'INNER JOIN', 'page_id = vote_page_id' )
 187+ )
 188+ );
 189+
 190+ $popularBlogPosts = array();
 191+ foreach ( $res as $row ) {
 192+ $popularBlogPosts[] = array(
 193+ 'title' => $row->page_title,
 194+ 'ns' => $row->page_namespace,
 195+ 'id' => $row->page_id
 196+ );
 197+ }
 198+
 199+ // Cache in memcached for 15 minutes
 200+ $wgMemc->set( $key, $popularBlogPosts, 60 * 15 );
 201+ }
 202+
 203+ $imgPath = $wgScriptPath . '/extensions/BlogPage/images/';
 204+
 205+ $output = '<div class="listpages-container">';
 206+ if ( empty( $popularBlogPosts ) ) {
 207+ $output .= wfMsg( 'ah-no-results' );
 208+ } else {
 209+ foreach( $popularBlogPosts as $popularBlogPost ) {
 210+ $titleObj = Title::makeTitle( NS_BLOG, $popularBlogPost['title'] );
 211+ $output .= '<div class="listpages-item">';
 212+ $pageImage = BlogPage::getPageImage( $popularBlogPost['id'] );
 213+ if( $pageImage ) {
 214+ // Load MediaWiki image object to get thumbnail tag
 215+ $img = wfFindFile( $pageImage );
 216+ $imgTag = '';
 217+ if ( is_object( $img ) ) {
 218+ $thumb = $img->getThumbnail( 65, 0, true );
 219+ $imgTag = $thumb->toHtml();
 220+ }
 221+
 222+ $output .= "<div class=\"listpages-image\">{$imgTag}</div>\n";
 223+ }
 224+ $output .= '<a href="' . $titleObj->escapeFullURL() . '">' .
 225+ $titleObj->getText() .
 226+ '</a>
 227+ <div class="listpages-date">';
 228+ $output .= '(' . wfMsg( 'blog-created-ago',
 229+ BlogPage::getTimeAgo(
 230+ // need to strtotime() it because getCreateDate() now
 231+ // returns the raw timestamp from the database; in the past
 232+ // it converted it to UNIX timestamp via the SQL function
 233+ // UNIX_TIMESTAMP but that was no good for our purposes
 234+ strtotime( BlogPage::getCreateDate( $popularBlogPost['id'] ) )
 235+ ) ) . ')';
 236+ $output .= "</div>
 237+ <div class=\"listpages-blurb\">\n" .
 238+ BlogPage::getBlurb(
 239+ $popularBlogPost['title'],
 240+ $popularBlogPost['ns'],
 241+ 300
 242+ ) .
 243+ '</div><!-- .listpages-blurb -->
 244+ <div class="listpages-stats">' . "\n";
 245+ $output .= "<img src=\"{$imgPath}voteIcon.gif\" alt=\"\" border=\"0\" /> " .
 246+ wfMsgExt(
 247+ 'blog-author-votes',
 248+ 'parsemag',
 249+ BlogPage::getVotesForPage( $popularBlogPost['id'] )
 250+ );
 251+ $output .= " <img src=\"{$imgPath}comment.gif\" alt=\"\" border=\"0\" /> " .
 252+ wfMsgExt(
 253+ 'blog-author-comments',
 254+ 'parsemag',
 255+ BlogPage::getCommentsForPage( $popularBlogPost['id'] )
 256+ ) . '</div><!-- . listpages-stats -->
 257+ </div><!-- .listpages-item -->
 258+ <div class="cleared"></div>' . "\n";
 259+ }
 260+ }
 261+
 262+ $output .= '</div>' . "\n"; // .listpages-container
 263+
 264+ return $output;
 265+ }
 266+
 267+ /**
 268+ * Get the list of the most voted pages within the last 72 hours.
 269+ *
 270+ * @param $dateCategories String: last three days (localized), separated
 271+ * by commas
 272+ * @return String: HTML
 273+ */
 274+ function displayMostVotedPages( $dateCategories ) {
 275+ global $wgMemc;
 276+
 277+ // Try cache first
 278+ $key = wfMemcKey( 'blog', 'mostvoted', 'ten' );
 279+ $data = $wgMemc->get( $key );
 280+
 281+ if( $data != '' ) {
 282+ wfDebugLog( 'BlogPage', 'Got most voted posts in ArticlesHome from cache' );
 283+ $votedBlogPosts = $data;
 284+ } else {
 285+ wfDebugLog( 'BlogPage', 'Got most voted posts in ArticlesHome from DB' );
 286+ $dbr = wfGetDB( DB_SLAVE );
 287+ $kaboom = explode( ',', $dateCategories );
 288+ // Without constructing Titles for all the categories, they won't
 289+ // have the underscores and thus the query will never match
 290+ // anything...thankfully getDBkey returns the title with the
 291+ // underscores
 292+ $titleOne = Title::makeTitle( NS_CATEGORY, $kaboom[0] );
 293+ $titleTwo = Title::makeTitle( NS_CATEGORY, $kaboom[1] );
 294+ $titleThree = Title::makeTitle( NS_CATEGORY, $kaboom[2] );
 295+ $res = $dbr->select(
 296+ array( 'page', 'categorylinks', 'Vote' ),
 297+ array( 'DISTINCT page_id', 'page_title', 'page_namespace' ),
 298+ array(
 299+ 'cl_to' => array(
 300+ $titleOne->getDBkey(), $titleTwo->getDBkey(),
 301+ $titleThree->getDBkey()
 302+ ),
 303+ 'page_namespace' => NS_BLOG,
 304+ 'page_id = vote_page_id',
 305+ 'vote_date < "' . date( 'Y-m-d H:i:s' ) . '"'
 306+ ),
 307+ __METHOD__,
 308+ array( 'LIMIT' => 10 ),
 309+ array(
 310+ 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' ),
 311+ 'Vote' => array( 'LEFT JOIN', 'vote_page_id = page_id' ),
 312+ )
 313+ );
 314+
 315+ $votedBlogPosts = array();
 316+ foreach ( $res as $row ) {
 317+ $votedBlogPosts[] = array(
 318+ 'title' => $row->page_title,
 319+ 'ns' => $row->page_namespace,
 320+ 'id' => $row->page_id
 321+ );
 322+ }
 323+
 324+ // Cache in memcached for 15 minutes
 325+ $wgMemc->set( $key, $votedBlogPosts, 60 * 15 );
 326+ }
 327+
 328+ // Here we output HTML
 329+ $output = '<div class="listpages-container">' . "\n";
 330+
 331+ if ( empty( $votedBlogPosts ) ) {
 332+ $output .= wfMsg( 'ah-no-results' );
 333+ } else {
 334+ foreach ( $votedBlogPosts as $votedBlogPost ) {
 335+ $titleObj = Title::makeTitle( NS_BLOG, $votedBlogPost['title'] );
 336+ $votes = BlogPage::getVotesForPage( $votedBlogPost['id'] );
 337+ $output .= '<div class="listpages-item">' . "\n";
 338+ $output .= '<div class="listpages-votebox">' . "\n";
 339+ $output .= '<div class="listpages-votebox-number">' .
 340+ $votes . "</div>\n";
 341+ $output .= '<div class="listpages-votebox-text">' .
 342+ wfMsgExt(
 343+ 'blog-author-votes',
 344+ 'parsemag',
 345+ $votes
 346+ ) . "</div>\n"; // .listpages-votebox-text
 347+ $output .= '</div>' . "\n"; // .listpages-votebox
 348+ $output .= '</div>' . "\n"; // .listpages-item
 349+ $output .= '<a href="' . $titleObj->escapeFullURL() . '">' .
 350+ $titleObj->getText() . '</a>';
 351+ $output .= '<div class="cleared"></div>';
 352+ }
 353+ }
 354+
 355+ $output .= "</div>\n"; // .listpages-container
 356+
 357+ return $output;
 358+ }
 359+
 360+ /**
 361+ * Get the list of the most commented pages within the last 72 hours.
 362+ *
 363+ * @param $dateCategories String: last three days (localized), separated
 364+ * by commas
 365+ * @return String: HTML
 366+ */
 367+ function displayMostCommentedPages( $dateCategories ) {
 368+ global $wgMemc;
 369+
 370+ // Try cache first
 371+ $key = wfMemcKey( 'blog', 'mostcommented', 'ten' );
 372+ $data = $wgMemc->get( $key );
 373+
 374+ if( $data != '' ) {
 375+ wfDebugLog( 'BlogPage', 'Got most commented posts in ArticlesHome from cache' );
 376+ $commentedBlogPosts = $data;
 377+ } else {
 378+ wfDebugLog( 'BlogPage', 'Got most commented posts in ArticlesHome from DB' );
 379+ $dbr = wfGetDB( DB_SLAVE );
 380+ $kaboom = explode( ',', $dateCategories );
 381+ // Without constructing Titles for all the categories, they won't
 382+ // have the underscores and thus the query will never match
 383+ // anything...thankfully getDBkey returns the title with the
 384+ // underscores
 385+ $titleOne = Title::makeTitle( NS_CATEGORY, $kaboom[0] );
 386+ $titleTwo = Title::makeTitle( NS_CATEGORY, $kaboom[1] );
 387+ $titleThree = Title::makeTitle( NS_CATEGORY, $kaboom[2] );
 388+ $res = $dbr->select(
 389+ array( 'page', 'categorylinks', 'Comments' ),
 390+ array( 'DISTINCT page_id', 'page_title', 'page_namespace' ),
 391+ array(
 392+ 'cl_to' => array(
 393+ $titleOne->getDBkey(), $titleTwo->getDBkey(),
 394+ $titleThree->getDBkey()
 395+ ),
 396+ 'page_namespace' => NS_BLOG,
 397+ 'page_id = Comment_Page_ID',
 398+ 'Comment_Date < "' . date( 'Y-m-d H:i:s' ) . '"'
 399+ ),
 400+ __METHOD__,
 401+ array( 'LIMIT' => 10 ),
 402+ array(
 403+ 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' ),
 404+ 'Comments' => array( 'LEFT JOIN', 'Comment_Page_ID = page_id' ),
 405+ )
 406+ );
 407+
 408+ $commentedBlogPosts = array();
 409+ foreach ( $res as $row ) {
 410+ $commentedBlogPosts[] = array(
 411+ 'title' => $row->page_title,
 412+ 'ns' => $row->page_namespace,
 413+ 'id' => $row->page_id
 414+ );
 415+ }
 416+
 417+ // Cache in memcached for 15 minutes
 418+ $wgMemc->set( $key, $commentedBlogPosts, 60 * 15 );
 419+ }
 420+
 421+ $output = '<div class="listpages-container">';
 422+
 423+ if ( empty( $commentedBlogPosts ) ) {
 424+ $output .= wfMsg( 'ah-no-results' );
 425+ } else {
 426+ foreach( $commentedBlogPosts as $commentedBlogPost ) {
 427+ $titleObj = Title::makeTitle( NS_BLOG, $commentedBlogPost['title'] );
 428+ $output .= '<div class="listpages-item">
 429+ <div class="listpages-votebox">
 430+ <div class="listpages-commentbox-number">' .
 431+ BlogPage::getCommentsForPage( $commentedBlogPost['id'] ) .
 432+ '</div>
 433+ </div>
 434+ <a href="' . $titleObj->escapeFullURL() . '">' .
 435+ $titleObj->getText() .
 436+ '</a>
 437+ </div><!-- .listpages-item -->
 438+ <div class="cleared"></div>' . "\n";
 439+ }
 440+ }
 441+
 442+ $output .= '</div>' . "\n"; // .listpages-container
 443+
 444+ return $output;
 445+ }
 446+
 447+ /**
 448+ * Get the list of the ten newest pages in the NS_BLOG namespace.
 449+ * This is used in the right side of the special page.
 450+ *
 451+ * @return String: HTML
 452+ */
 453+ function displayNewestPages() {
 454+ global $wgMemc;
 455+
 456+ // Try cache first
 457+ $key = wfMemcKey( 'blog', 'newest', 'ten' );
 458+ $data = $wgMemc->get( $key );
 459+
 460+ if( $data != '' ) {
 461+ wfDebugLog( 'BlogPage', 'Got new articles in ArticlesHome from cache' );
 462+ $newBlogPosts = $data;
 463+ } else {
 464+ wfDebugLog( 'BlogPage', 'Got new articles in ArticlesHome from DB' );
 465+ $dbr = wfGetDB( DB_SLAVE );
 466+ // Code sporked from Rob Church's NewestPages extension
 467+ $res = $dbr->select(
 468+ 'page',
 469+ array(
 470+ 'page_namespace', 'page_title', 'page_is_redirect',
 471+ 'page_id'
 472+ ),
 473+ array( 'page_namespace' => NS_BLOG, 'page_is_redirect' => 0 ),
 474+ __METHOD__,
 475+ array( 'ORDER BY' => 'page_id DESC', 'LIMIT' => 10 )
 476+ );
 477+
 478+ $newBlogPosts = array();
 479+ foreach ( $res as $row ) {
 480+ $newBlogPosts[] = array(
 481+ 'title' => $row->page_title,
 482+ 'ns' => $row->page_namespace,
 483+ 'id' => $row->page_id
 484+ );
 485+ }
 486+
 487+ // Cache in memcached for 15 minutes
 488+ $wgMemc->set( $key, $newBlogPosts, 60 * 15 );
 489+ }
 490+
 491+ $output = '<div class="listpages-container">' . "\n";
 492+ if ( empty( $newBlogPosts ) ) {
 493+ $output .= wfMsg( 'ah-no-results' );
 494+ } else {
 495+ foreach( $newBlogPosts as $newBlogPost ) {
 496+ $titleObj = Title::makeTitle( NS_BLOG, $newBlogPost['title'] );
 497+ $votes = BlogPage::getVotesForPage( $newBlogPost['id'] );
 498+ $output .= "\t\t\t\t" . '<div class="listpages-item">';
 499+ $output .= '<div class="listpages-votebox">' . "\n";
 500+ $output .= '<div class="listpages-votebox-number">' .
 501+ $votes .
 502+ "</div>\n"; // .listpages-votebox-number
 503+ $output .= '<div class="listpages-votebox-text">' .
 504+ wfMsgExt(
 505+ 'blog-author-votes',
 506+ 'parsemag',
 507+ $votes
 508+ ) . "</div>\n"; // .listpages-votebox-text
 509+ $output .= "</div>\n"; // .listpages-votebox
 510+ $output .= '<a href="' . $titleObj->escapeFullURL() . '">' .
 511+ $titleObj->getText() .
 512+ '</a>
 513+ </div><!-- .listpages-item -->
 514+ <div class="cleared"></div>' . "\n";
 515+ }
 516+ }
 517+ $output .= '</div>' . "\n"; // .listpages-container
 518+ return $output;
 519+ }
 520+
 521+ /**
 522+ * Get the 25 newest blog posts from the database and then cache them in
 523+ * memcached for 15 minutes.
 524+ *
 525+ * @return String: HTML
 526+ */
 527+ public function getNewestPosts() {
 528+ global $wgMemc, $wgScriptPath;
 529+
 530+ // Try cache first
 531+ $key = wfMemcKey( 'blog', 'newest', 'twentyfive' );
 532+ $data = $wgMemc->get( $key );
 533+
 534+ if( $data != '' ) {
 535+ wfDebugLog( 'BlogPage', 'Got newest posts in ArticlesHome from cache' );
 536+ $newestBlogPosts = $data;
 537+ } else {
 538+ wfDebugLog( 'BlogPage', 'Got newest posts in ArticlesHome from DB' );
 539+ $dbr = wfGetDB( DB_SLAVE );
 540+ // Code sporked from Rob Church's NewestPages extension
 541+ $res = $dbr->select(
 542+ array( 'page' ),
 543+ array(
 544+ 'page_namespace', 'page_title', 'page_is_redirect',
 545+ 'page_id',
 546+ ),
 547+ array(
 548+ 'page_namespace' => NS_BLOG,
 549+ 'page_is_redirect' => 0,
 550+ ),
 551+ __METHOD__,
 552+ array(
 553+ 'ORDER BY' => 'page_id DESC',
 554+ 'LIMIT' => 25
 555+ )
 556+ );
 557+
 558+ $newestBlogPosts = array();
 559+ foreach ( $res as $row ) {
 560+ $newestBlogPosts[] = array(
 561+ 'title' => $row->page_title,
 562+ 'ns' => $row->page_namespace,
 563+ 'id' => $row->page_id
 564+ );
 565+ }
 566+
 567+ // Cache in memcached for 15 minutes
 568+ $wgMemc->set( $key, $newestBlogPosts, 60 * 15 );
 569+ }
 570+
 571+ $imgPath = $wgScriptPath . '/extensions/BlogPage/images/';
 572+
 573+ $output = '<div class="listpages-container">';
 574+ if ( empty( $newestBlogPosts ) ) {
 575+ $output .= wfMsg( 'ah-no-results' );
 576+ } else {
 577+ foreach( $newestBlogPosts as $newestBlogPost ) {
 578+ $titleObj = Title::makeTitle( NS_BLOG, $newestBlogPost['title'] );
 579+ $output .= '<div class="listpages-item">';
 580+ $pageImage = BlogPage::getPageImage( $newestBlogPost['id'] );
 581+ if( $pageImage ) {
 582+ // Load MediaWiki image object to get thumbnail tag
 583+ $img = wfFindFile( $pageImage );
 584+ $imgTag = '';
 585+ if ( is_object( $img ) ) {
 586+ $thumb = $img->getThumbnail( 65, 0, true );
 587+ $imgTag = $thumb->toHtml();
 588+ }
 589+
 590+ $output .= "<div class=\"listpages-image\">{$imgTag}</div>\n";
 591+ }
 592+ $output .= '<a href="' . $titleObj->escapeFullURL() . '">' .
 593+ $titleObj->getText() .
 594+ '</a>
 595+ <div class="listpages-date">';
 596+ $output .= '(' . wfMsg( 'blog-created-ago',
 597+ BlogPage::getTimeAgo(
 598+ // need to strtotime() it because getCreateDate() now
 599+ // returns the raw timestamp from the database; in the past
 600+ // it converted it to UNIX timestamp via the SQL function
 601+ // UNIX_TIMESTAMP but that was no good for our purposes
 602+ strtotime( BlogPage::getCreateDate( $newestBlogPost['id'] ) )
 603+ ) ) . ')';
 604+ $output .= "</div>
 605+ <div class=\"listpages-blurb\">\n" .
 606+ BlogPage::getBlurb(
 607+ $newestBlogPost['title'],
 608+ $newestBlogPost['ns'],
 609+ 300
 610+ ) .
 611+ '</div><!-- .listpages-blurb -->
 612+ <div class="listpages-stats">' . "\n";
 613+ $output .= "<img src=\"{$imgPath}voteIcon.gif\" alt=\"\" border=\"0\" /> " .
 614+ wfMsgExt(
 615+ 'blog-author-votes',
 616+ 'parsemag',
 617+ BlogPage::getVotesForPage( $newestBlogPost['id'] )
 618+ );
 619+ $output .= " <img src=\"{$imgPath}comment.gif\" alt=\"\" border=\"0\" /> " .
 620+ wfMsgExt(
 621+ 'blog-author-comments',
 622+ 'parsemag',
 623+ BlogPage::getCommentsForPage( $newestBlogPost['id'] )
 624+ ) . '</div><!-- . listpages-stats -->
 625+ </div><!-- .listpages-item -->
 626+ <div class="cleared"></div>' . "\n";
 627+ }
 628+ }
 629+
 630+ $output .= '</div>' . "\n"; // .listpages-container
 631+
 632+ return $output;
 633+ }
 634+
 635+ /**
 636+ * Get the 25 most popular blog posts from the database and then cache them
 637+ * in memcached for 15 minutes.
 638+ * The definition of 'popular' is very arbitrary at the moment.
 639+ *
 640+ * Fork of the original getPopularPosts() method, the only thing changed
 641+ * here is the HTML output which was toned down and count changed from 25
 642+ * to 10.
 643+ *
 644+ * @return String: HTML
 645+ */
 646+ public function getPopularPostsForRightSide() {
 647+ global $wgMemc;
 648+
 649+ // Try cache first
 650+ $key = wfMemcKey( 'blog', 'popular', 'ten' );
 651+ $data = $wgMemc->get( $key );
 652+
 653+ if( $data != '' ) {
 654+ wfDebugLog( 'BlogPage', 'Got popular posts in ArticlesHome from cache' );
 655+ $popularBlogPosts = $data;
 656+ } else {
 657+ wfDebugLog( 'BlogPage', 'Got popular posts in ArticlesHome from DB' );
 658+ $dbr = wfGetDB( DB_SLAVE );
 659+ $commentsTable = $dbr->tableName( 'Comments' );
 660+ $voteTable = $dbr->tableName( 'Vote' );
 661+ // Code sporked from Rob Church's NewestPages extension
 662+ $res = $dbr->select(
 663+ array( 'page', 'Comments', 'Vote' ),
 664+ array(
 665+ 'DISTINCT page_id', 'page_namespace', 'page_title',
 666+ 'page_is_redirect',
 667+ ),
 668+ array(
 669+ 'page_namespace' => NS_BLOG,
 670+ 'page_is_redirect' => 0,
 671+ 'page_id = Comment_Page_ID',
 672+ 'page_id = vote_page_id',
 673+ // If you can figure out how to do this without a subquery,
 674+ // please let me know. Until that...
 675+ "((SELECT COUNT(*) FROM $voteTable WHERE vote_page_id = page_id) >= 5 OR
 676+ (SELECT COUNT(*) FROM $commentsTable WHERE Comment_Page_ID = page_id) >= 5)",
 677+ ),
 678+ __METHOD__,
 679+ array(
 680+ 'ORDER BY' => 'page_id DESC',
 681+ 'LIMIT' => 10
 682+ ),
 683+ array(
 684+ 'Comments' => array( 'INNER JOIN', 'page_id = Comment_Page_ID' ),
 685+ 'Vote' => array( 'INNER JOIN', 'page_id = vote_page_id' )
 686+ )
 687+ );
 688+
 689+ $popularBlogPosts = array();
 690+ foreach ( $res as $row ) {
 691+ $popularBlogPosts[] = array(
 692+ 'title' => $row->page_title,
 693+ 'ns' => $row->page_namespace,
 694+ 'id' => $row->page_id
 695+ );
 696+ }
 697+
 698+ // Cache in memcached for 15 minutes
 699+ $wgMemc->set( $key, $popularBlogPosts, 60 * 15 );
 700+ }
 701+
 702+ $output = '<div class="listpages-container">';
 703+ if ( empty( $popularBlogPosts ) ) {
 704+ $output .= wfMsg( 'ah-no-results' );
 705+ } else {
 706+ foreach( $popularBlogPosts as $popularBlogPost ) {
 707+ $titleObj = Title::makeTitle( NS_BLOG, $popularBlogPost['title'] );
 708+ $votes = BlogPage::getVotesForPage( $popularBlogPost['id'] );
 709+ $output .= '<div class="listpages-item">';
 710+ $output .= '<div class="listpages-votebox">' . "\n";
 711+ $output .= '<div class="listpages-votebox-number">' .
 712+ $votes . "</div>\n";
 713+ $output .= '<div class="listpages-votebox-text">' .
 714+ wfMsgExt(
 715+ 'blog-author-votes',
 716+ 'parsemag',
 717+ $votes
 718+ ) . "</div>\n"; // .listpages-votebox-text
 719+ $output .= '</div>' . "\n"; // .listpages-votebox
 720+ $output .= '<a href="' . $titleObj->escapeFullURL() . '">' .
 721+ $titleObj->getText() .
 722+ '</a>
 723+ </div><!-- .listpages-item -->
 724+ <div class="cleared"></div>' . "\n";
 725+ }
 726+ }
 727+
 728+ $output .= '</div>' . "\n"; // .listpages-container
 729+
 730+ return $output;
 731+ }
 732+}
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/SpecialArticlesHome.php
___________________________________________________________________
Added: svn:eol-style
1733 + native
Index: trunk/extensions/BlogPage/Blog.i18n.php
@@ -0,0 +1,157 @@
 2+<?php
 3+/**
 4+ * Internationalization file for the BlogPage extension.
 5+ *
 6+ * @file
 7+ * @ingroup Extensions
 8+ */
 9+
 10+$messages = array();
 11+
 12+/** English
 13+ * @author David Pean
 14+ */
 15+$messages['en'] = array(
 16+ 'blog-and' => 'and',
 17+ 'blog-anonymous-name' => 'Anonymous Fanatic',
 18+ 'blog-author-comments' => '$1 {{PLURAL:$1|comment|comments}}',
 19+ 'blog-author-more-by' => 'More By $1', // $1 is a user name
 20+ 'blog-author-points' => '$1 pts',
 21+ 'blog-author-title' => 'About the {{PLURAL:$1|Author|Authors}}',
 22+ 'blog-author-votes' => '$1 {{PLURAL:$1|vote|votes}}',
 23+ 'blog-by' => 'by',
 24+ 'blog-by-user-category' => '$1 by User', // $1 is MediaWiki:Blog-category
 25+ 'blog-category' => 'Articles', // replaces $wgBlogCategory config variable
 26+ 'blog-comments-of-day' => 'Comments of the Day',
 27+ 'blog-created' => 'created $1',
 28+ 'blog-created-ago' => 'created $1 ago',
 29+ 'blog-embed-title' => 'Embed This On Your Site',
 30+ 'blog-in-the-news' => 'In the News',
 31+ 'blog-last-edited' => 'last edited $1',
 32+ 'blog-login' => 'You have to [[Special:UserLogin|log in]] to create articles.',
 33+ 'blog-login-edit' => 'You have to [[Special:UserLogin|log in]] to edit blog articles.',
 34+ 'blog-new-articles' => 'New Articles',
 35+ 'blog-permission-required' => 'You do not have the permission to create articles.',
 36+ 'blog-popular-articles' => 'Don\'t Miss',
 37+ 'blog-multiple-authors' => 'This article was written collaboratively by $1',
 38+ 'blog-recent-editors' => 'Other recent contributors',
 39+ 'blog-recent-editors-message' => 'Make this page better by editing it.',
 40+ 'blog-recent-voters' => 'Other recent voters',
 41+ 'blog-recent-voters-message' => 'If you like the article, vote for it.',
 42+ 'blog-view-archive-link' => 'View All',
 43+ // Duplicates RandomGameUnit's messages, I dunno why
 44+ 'game-unit-quiz-title' => 'Never Ending Quiz',
 45+ 'game-unit-poll-title' => 'Take a Poll',
 46+ 'game-unit-picturegame-title' => 'Play the Picture Game',
 47+ // ArticlesHome
 48+ // title of Special:ArticleLists, as shown on Special:SpecialPages.
 49+ // I could've used ah-new-articles but I figured that people will mistake
 50+ // it to wiki articles as opposed to blog articles so I decided to create a
 51+ // new message instead.
 52+ 'articlelists' => 'New Blog Articles',
 53+ 'ah-no-results' => 'No pages found.',
 54+ 'ah-popular-articles' => 'Popular Articles',
 55+ 'ah-new-articles' => 'New Articles',
 56+ 'ah-rss-feed' => 'RSS feed',
 57+ 'ah-write-article' => 'Write an Article',
 58+ 'ah-todays-articles' => 'Today\'s Articles',
 59+ 'ah-most-votes' => 'Most Votes (72 hours)',
 60+ 'ah-what-talking-about' => 'Most Comments (72 hours)',
 61+ 'articleshome' => 'Blog Homepage',
 62+ // Special:CreateBlogPost
 63+ 'createblogpost' => 'Create Blog Post',
 64+ 'blog-create-rules' => '', // rules shown above the title field; do not translate!
 65+ 'blog-tagcloud-blacklist' => '', // list of categories that won't be shown on the tagcloud; format is: * cat name\n* another
 66+ 'blog-create-category-help' => 'Categories help organize information on the site. To add multiple categories, separate them by commas.',
 67+ 'blog-create-title' => 'Title',
 68+ 'blog-create-text' => 'Text',
 69+ 'blog-create-categories' => 'Categories',
 70+ 'blog-create-button' => 'Create!',
 71+ 'blog-create-summary' => 'New blog post created.',
 72+ 'blog-create-error-need-content' => "'''Error:''' Your blog post must have some content!",
 73+ 'blog-create-error-need-title' => "'''Error:''' You need to supply a title for the blog post!",
 74+ 'blog-create-error-page-exists' => "'''Error:''' There is already a blog post with that title. Please choose a different title for your blog post.",
 75+ 'blog-js-create-error-need-content' => 'Your blog post must have some content!',
 76+ 'blog-js-create-error-need-title' => 'You need to supply a title for the blog post!',
 77+ 'blog-js-create-error-page-exists' => 'There is already a blog post with that title. Please choose a different title for your blog post.',
 78+ // Ported from ListPages
 79+ 'blog-more' => 'more',
 80+ 'blog-time-ago' => '$1 ago',
 81+ 'blog-time-days' => '{{PLURAL:$1|one day|$1 days}}',
 82+ 'blog-time-hours' => '{{PLURAL:$1|one hour|$1 hours}}',
 83+ 'blog-time-minutes' => '{{PLURAL:$1|one minute|$1 minutes}}',
 84+ 'blog-time-seconds' => '{{PLURAL:$1|one second|$1 seconds}}',
 85+ 'right-createblogpost' => '[[Special:CreateBlogPost|Create new blog posts]]',
 86+ // Integration with UserProfile
 87+ // These messages didn't originally have the blog- prefix
 88+ 'blog-user-articles-title' => 'Blogs',
 89+ 'blog-user-articles-votes' => '{{PLURAL:$1|one vote|$1 votes}}',
 90+ 'blog-user-article-comment' => '{{PLURAL:$1|one comment|$1 comments}}',
 91+);
 92+
 93+/** Finnish (Suomi)
 94+ * @author Jack Phoenix <jack@countervandalism.net>
 95+ */
 96+$messages['fi'] = array(
 97+ 'blog-and' => 'ja',
 98+ 'blog-anonymous-name' => 'Anonyymi fanaatikko',
 99+ 'blog-author-comments' => '$1 {{PLURAL:$1|kommentti|kommenttia}}',
 100+ 'blog-author-more-by' => 'Lisää kirjoituksia käyttäjältä $1',
 101+ 'blog-author-title' => 'Tietoa {{PLURAL:$1|tekijästä|tekijöistä}}',
 102+ 'blog-author-votes' => '$1 {{PLURAL:$1|ääni|ääntä}}',
 103+ 'blog-by' => 'kirjoittanut',
 104+ 'blog-by-user-category' => '$1, jotka on kirjoittanut',
 105+ 'blog-category' => 'Artikkelit',
 106+ 'blog-comments-of-day' => 'Päivän kommentit',
 107+ 'blog-created' => 'luotu $1',
 108+ 'blog-created-ago' => 'luotu $1 sitten',
 109+ 'blog-in-the-news' => 'Uutisissa',
 110+ 'blog-last-edited' => 'viimeisin muokkaus $1',
 111+ 'blog-login' => 'Sinun tulee [[Special:UserLogin|kirjautua sisään]] luodaksesi artikkeleita.',
 112+ 'blog-login-edit' => 'Sinun tulee [[Special:UserLogin|kirjautua sisään]] muokataksesi blogiartikkeleita.',
 113+ 'blog-more' => 'lisää',
 114+ 'blog-new-articles' => 'Uudet artikkelit',
 115+ 'blog-permission-required' => 'Sinulla ei ole oikeutta luoda artikkeleita.',
 116+ 'blog-popular-articles' => 'Älä unohda',
 117+ 'blog-multiple-authors' => 'Tämän artikkelin kirjoittivat yhdessä $1',
 118+ 'blog-recent-editors' => 'Muut tuoreet muokkaajat',
 119+ 'blog-recent-editors-message' => 'Tee tästä sivusta parempi muokkaamalla sitä.',
 120+ 'blog-recent-voters' => 'Muut tuoreet äänestäjät',
 121+ 'blog-recent-voters-message' => 'Jos pidit artikkelista, äänestä sitä.',
 122+ 'blog-time-ago' => '$1 sitten',
 123+ 'blog-time-days' => '{{PLURAL:$1|päivä|$1 päivää}}',
 124+ 'blog-time-hours' => '{{PLURAL:$1|tunti|$1 tuntia}}',
 125+ 'blog-time-minutes' => '{{PLURAL:$1|minuutti|$1 minuuttia}}',
 126+ 'blog-time-seconds' => '{{PLURAL:$1|sekunti|$1 sekuntia}}',
 127+ 'blog-view-archive-link' => 'Katso kaikki',
 128+ 'game-unit-poll-title' => 'Ota osaa äänestykseen',
 129+ 'game-unit-quiz-title' => 'Pelaa tietovisapeliä',
 130+ 'game-unit-picturegame-title' => 'Pelaa kuvapeliä',
 131+ 'articlelists' => 'Uudet blogiartikkelit',
 132+ 'ah-no-results' => 'Sivuja ei löytynyt.',
 133+ 'ah-popular-articles' => 'Suositut artikkelit',
 134+ 'ah-new-articles' => 'Uudet artikkelit',
 135+ 'ah-rss-feed' => 'RSS-syöte',
 136+ 'ah-write-article' => 'Kirjoita artikkeli',
 137+ 'ah-todays-articles' => 'Tämänpäiväiset artikkelit',
 138+ 'ah-most-votes' => 'Eniten ääniä (72 tuntia)',
 139+ 'ah-what-talking-about' => 'Eniten kommentteja (72 tuntia)',
 140+ 'articleshome' => 'Blogien kotisivu',
 141+ 'createblogpost' => 'Luo blogikirjoitus',
 142+ 'blog-create-category-help' => 'Luokat auttavat järjestämään tämän sivuston tietoa. Lisätäksesi useampia luokkia erottele ne pilkuin.',
 143+ 'blog-create-title' => 'Otsikko',
 144+ 'blog-create-text' => 'Teksti',
 145+ 'blog-create-categories' => 'Luokat',
 146+ 'blog-create-button' => 'Luo!',
 147+ 'blog-create-summary' => 'Uusi blogikirjoitus luotu.',
 148+ 'blog-create-error-need-content' => "'''Virhe:''' Blogikirjoituksellasi tulee olla myös sisältöä!",
 149+ 'blog-create-error-need-title' => "'''Virhe:''' Sinun tulee antaa otsikko blogikirjoituksellesi!",
 150+ 'blog-create-error-page-exists' => "'''Virhe:''' Olemassaolevalla blogikirjoituksella on jo sama otsikko. Annathan erilaisen otsikon blogikirjoituksellesi.",
 151+ 'blog-js-create-error-need-content' => 'Blogikirjoituksellasi tulee olla myös sisältöä!',
 152+ 'blog-js-create-error-need-title' => 'Sinun tulee antaa otsikko blogikirjoituksellesi!',
 153+ 'blog-js-create-error-page-exists' => 'Olemassaolevalla blogikirjoituksella on jo sama otsikko. Annathan erilaisen otsikon blogikirjoituksellesi.',
 154+ 'right-createblogpost' => '[[Special:CreateBlogPost|Luoda uusia blogikirjoituksia]]',
 155+ 'blog-user-articles-title' => 'Blogit',
 156+ 'blog-user-articles-votes' => '{{PLURAL:$1|yksi ääni|$1 ääntä}}',
 157+ 'blog-user-article-comment' => '{{PLURAL:$1|yksi kommentti|$1 kommenttia}}',
 158+);
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/Blog.i18n.php
___________________________________________________________________
Added: svn:eol-style
1159 + native
Index: trunk/extensions/BlogPage/Blog.namespaces.php
@@ -0,0 +1,65 @@
 2+<?php
 3+/**
 4+ * Translations of the Blog namespace.
 5+ *
 6+ * @file
 7+ */
 8+
 9+$namespaceNames = array();
 10+
 11+// For wikis where the BlogPage extension is not installed.
 12+if( !defined( 'NS_BLOG' ) ) {
 13+ define( 'NS_BLOG', 500 );
 14+}
 15+
 16+if( !defined( 'NS_BLOG_TALK' ) ) {
 17+ define( 'NS_BLOG_TALK', 501 );
 18+}
 19+
 20+/** English */
 21+$namespaceNames['en'] = array(
 22+ NS_BLOG => 'Blog',
 23+ NS_BLOG_TALK => 'Blog_talk',
 24+);
 25+
 26+/** German (Deutsch) */
 27+$namespaceNames['de'] = array(
 28+ NS_BLOG => 'Blog',
 29+ NS_BLOG_TALK => 'Blog_Diskussion'
 30+);
 31+
 32+/** Spanish (Español) */
 33+$namespaceNames['es'] = array(
 34+ NS_BLOG => 'Blog',
 35+ NS_BLOG_TALK => 'Blog_Discusión'
 36+);
 37+
 38+/** Finnish (Suomi) */
 39+$namespaceNames['fi'] = array(
 40+ NS_BLOG => 'Blogi',
 41+ NS_BLOG_TALK => 'Keskustelu_blogista',
 42+);
 43+
 44+/** Dutch (Nederlands) */
 45+$namespaceNames['nl'] = array(
 46+ NS_BLOG => 'Blog',
 47+ NS_BLOG_TALK => 'Overleg_blog',
 48+);
 49+
 50+/** Norwegian Nynorsk (‪Norsk (nynorsk)‬) */
 51+$namespaceNames['nn'] = array(
 52+ NS_BLOG => 'Blogg',
 53+ NS_BLOG_TALK => 'Bloggdiskusjon'
 54+);
 55+
 56+/** Norwegian (bokmål)‬ (‪Norsk (bokmål)‬) */
 57+$namespaceNames['no'] = array(
 58+ NS_BLOG => 'Blogg',
 59+ NS_BLOG_TALK => 'Bloggdiskusjon'
 60+);
 61+
 62+/** Russian (Русский) */
 63+$namespaceNames['ru'] = array(
 64+ NS_BLOG => 'Блог',
 65+ NS_BLOG_TALK => 'Обсуждение_блога'
 66+);
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/Blog.namespaces.php
___________________________________________________________________
Added: svn:eol-style
167 + native
Index: trunk/extensions/BlogPage/BlogPage.css
@@ -0,0 +1,379 @@
 2+h1.firstHeading {
 3+ display: none;
 4+}
 5+
 6+#main {
 7+ padding: 0px 0px 0px 0px !important;
 8+}
 9+
 10+#main p {
 11+ font-size: 12px;
 12+ font-weight: normal;
 13+ line-height: 16px;
 14+}
 15+
 16+#blog-page-left {
 17+ position: absolute;
 18+ right: 0px;
 19+ top: 0px;
 20+ width: 20%;
 21+}
 22+
 23+#blog-page-middle {
 24+ margin: 0px;
 25+ width: 75%;
 26+ float: none;
 27+ overflow: visible;
 28+ padding: 0px;
 29+}
 30+
 31+#blog-page-right {
 32+ float: right;/*left;*/
 33+ width: 179px;
 34+ padding: 8px 0px 0px 0px;
 35+ background-color: #F2F4F7;
 36+ border-left: 1px solid #dcdcdc;
 37+ border-bottom: 1px solid #dcdcdc;
 38+ padding: 10px;
 39+ overflow: hidden;
 40+}
 41+
 42+#blog-page-middle h1.page-title {
 43+ margin: 5px 0px 0px 0px !important;
 44+ line-height: 22px;
 45+}
 46+
 47+#blog-page-middle #edit-menu {
 48+ margin: 0px 0px 0px 7px !important;
 49+}
 50+
 51+.blog-left-units {
 52+ background-color: #F2F4F7;
 53+ padding: 10px 10px 5px 10px;
 54+ border-bottom: 1px solid #dcdcdc;
 55+}
 56+
 57+.blog-container {
 58+ padding: 0px 0px 10px 0px;
 59+ margin: 0px 0px 10px 0px;
 60+ border-bottom: 1px solid #dcdcdc;
 61+}
 62+
 63+.blog-container h2 {
 64+ margin: 0px 0px 8px 0px !important;
 65+ font-size: 16px;
 66+ letter-spacing: -1px;
 67+ color: #333;
 68+}
 69+
 70+.bottom-fix {
 71+ border-bottom: none !important;
 72+ padding-bottom: 5px !important;
 73+ margin-bottom: 0px !important;
 74+}
 75+
 76+/* Author */
 77+.author-container-fix {
 78+ margin: 0px 0px 10px 0px;
 79+}
 80+
 81+.more-container {
 82+ margin: 0px 0px 10px 0px;
 83+ padding: 7px 0px 5px 0px;
 84+ border-bottom: 1px solid #dcdcdc;
 85+}
 86+
 87+.more-container-fix {
 88+ margin:0px !important;
 89+ border:none !important;
 90+}
 91+
 92+.blog-left-units h2 {
 93+ letter-spacing: -1px;
 94+ padding: 0px !important;
 95+ margin: 0px !important;
 96+ color: #333;
 97+}
 98+
 99+.author-info {
 100+ font-size: 11px;
 101+ color: #333;
 102+ line-height: 14px;
 103+ margin: 10px 0px 0px 0px;
 104+}
 105+
 106+.author-info img {
 107+ float: left;
 108+ border: 1px solid #dcdcdc;
 109+ padding: 3px;
 110+ background-color: #fff;
 111+ display: block;
 112+ text-align: justify;
 113+ margin: 0px 10px 0px 0px;
 114+}
 115+
 116+.author-articles {
 117+ margin: 4px 0px 0px 0px;
 118+}
 119+
 120+.more-container h3 {
 121+ margin: 0px 0px 5px 0px !important;
 122+ font-size: 11px;
 123+ letter-spacing: -1px;
 124+ color: #555;
 125+}
 126+
 127+.author-title {
 128+ margin: 0px 0px 6px 0px;
 129+}
 130+
 131+.author-title a {
 132+ text-decoration: none;
 133+ font-size: 14px;
 134+ font-weight: bold;
 135+}
 136+
 137+.author-article-item {
 138+ line-height: 12px;
 139+ margin: 0px 0px 4px 0px;
 140+}
 141+
 142+.author-article-item a {
 143+ text-decoration: none;
 144+ font-size: 11px;
 145+}
 146+
 147+.author-item-small {
 148+ font-size: 10px;
 149+ color: #888;
 150+}
 151+
 152+.author-archive-link {
 153+ text-align: right;
 154+ font-size: 10px;
 155+}
 156+
 157+/* Recent Container */
 158+.recent-container {
 159+ margin: 0px 0px 10px;
 160+ padding: 0px 0px 10px;
 161+ font-size: 11px;
 162+ border-bottom: 1px solid #dcdcdc;
 163+}
 164+
 165+.recent-container div {
 166+ font-size: 10px;
 167+ margin: 0px 0px 5px 0px;
 168+ line-height: 11px;
 169+ color: #888;
 170+}
 171+
 172+.recent-container h2 {
 173+ letter-spacing: -1px;
 174+ font-size: 14px;
 175+ margin: 0px 0px 1px 0px !important;
 176+ color: #333;
 177+}
 178+
 179+.recent-item {
 180+ margin: 0px 0px 0px 0px;
 181+}
 182+
 183+.recent-title {
 184+ color: #444;
 185+ font-size: 12px;
 186+ line-height: 13px;
 187+ font-weight: bold;
 188+ margin: 0px 0px 10px 0px;
 189+}
 190+
 191+.recent-container img {
 192+ padding: 2px;
 193+ border: 1px solid #dcdcdc;
 194+ background-color: #fff;
 195+ margin:2 px 2px 0px 0px
 196+}
 197+
 198+.recent-container a {
 199+ text-decoration: none;
 200+}
 201+
 202+/* Comment of the Day */
 203+.cod-score {
 204+ font-weight: bold;
 205+ color: #fff;
 206+ background-color: orange;
 207+ padding: 0px 3px;
 208+ margin: 0px 4px 0px 0px;
 209+}
 210+
 211+.cod-poster a {
 212+ font-size: 12px;
 213+ text-decoration: none;
 214+}
 215+
 216+.cod-comment a {
 217+ text-decoration: none;
 218+ font-size: 12px;
 219+}
 220+
 221+.cod-comment {
 222+ line-height: 13px;
 223+}
 224+
 225+.cod-item {
 226+ margin: 0px 0px 5px 0px;
 227+ overflow: hidden;
 228+}
 229+
 230+.cod-item img {
 231+ padding: 1px;
 232+ background-color: #fff;
 233+ border: 1px solid #dcdcdc;
 234+ vertical-align: middle;
 235+}
 236+
 237+/* Ad Unit */
 238+.article-ad {
 239+ margin: 10px 0px 0px 0px;
 240+ text-align: center;
 241+}
 242+
 243+.article-ad a {
 244+ text-align: center;
 245+}
 246+
 247+/* Casual Game */
 248+.game-unit-container {
 249+ padding: 0px 0px 10px 0px;
 250+ margin: 0px 0px 10px 0px;
 251+ border-bottom: 1px solid #dcdcdc;
 252+ overflow: hidden;
 253+}
 254+
 255+.game-unit-container h2 {
 256+ margin: 0px 0px 8px 0px !important;
 257+ font-size: 16px;
 258+ letter-spacing: -1px;
 259+ color: #333;
 260+}
 261+
 262+.poll-unit-title {
 263+ font-size: 12px;
 264+ line-height: 13px;
 265+ font-weight: bold;
 266+ color: #777;
 267+ margin: 0px 0px 8px 0px;
 268+}
 269+
 270+.poll-unit-choices a {
 271+ text-decoration: none;
 272+ font-weight: bold;
 273+ font-size: 12px;
 274+ display: block;
 275+}
 276+
 277+.poll-unit-image {
 278+ margin: 0px 0px 8px 0px;
 279+}
 280+
 281+.poll-unit-image img {
 282+ padding: 3px;
 283+ background-color: #fff;
 284+ border: 1px solid #dcdcdc;
 285+}
 286+
 287+.poll-unit-choices input {
 288+ margin: 0px 5px 0px 0px;
 289+}
 290+
 291+.quiz-unit-image {
 292+ margin: 8px 0px 0px 0px;
 293+}
 294+
 295+.quiz-unit-image img {
 296+ padding: 3px;
 297+ background-color: #fff;
 298+ border: 1px solid #dcdcdc;
 299+}
 300+
 301+.quiz-unit-title a {
 302+ font-size: 13px;
 303+ text-decoration: none;
 304+ font-weight: bold;
 305+}
 306+
 307+.pg-unit-title {
 308+ font-size: 12px;
 309+ line-height: 13px;
 310+ font-weight: bold;
 311+ color: #777;
 312+ margin: 0px 0px 8px 0px;
 313+}
 314+
 315+.pg-unit-pictures img {
 316+ float: left;
 317+ background-color: #fff;
 318+ border: 1px solid #dcdcdc;
 319+ padding: 3px;
 320+ margin: 0px 3px 0px 0px;
 321+}
 322+
 323+.pickem-unit-game {
 324+ /*border-bottom: 1px solid #dcdcdc;*/
 325+ padding: 8px 0px;
 326+}
 327+
 328+.pickem-unit-date {
 329+ font-weight: bold;
 330+}
 331+
 332+.pickem-unit-visitor, .pickem-unit-home {
 333+ margin: 5px 0px 0px 0px;
 334+ cursor: pointer;
 335+}
 336+
 337+.pickem-unit-visitor img, .pickem-unit-home img{
 338+ vertical-align: middle;
 339+ border: 1px solid #dcdcdc;
 340+ margin: 0px 5px 0px 0px;
 341+}
 342+
 343+.pickem-unit-nogame {
 344+ font-weight: bold;
 345+ color: #FF0000;
 346+}
 347+
 348+/* List Pages */
 349+#blog-page-right .listpages-item {
 350+ line-height: 14px;
 351+}
 352+
 353+#blog-page-right .listpages-item a {
 354+ font-size: 12px !important;
 355+ font-weight: normal !important;
 356+ text-decoration: none !important;
 357+}
 358+
 359+#blog-page-right .listpages-item {
 360+ margin: 0px 0px 6px 0px !important;
 361+}
 362+
 363+/* By Line */
 364+.blog-byline {
 365+ font-weight: bold;
 366+ margin: 8px 0px 0px 0px;
 367+ color: #555;
 368+ font-size: 13px;
 369+}
 370+
 371+.blog-byline-last-edited {
 372+ font-size: 11px;
 373+ color: #888;
 374+ margin: 0px 0px 15px 0px;
 375+}
 376+
 377+/* Categories */
 378+#categories {
 379+ padding: 10px 0px;
 380+}
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/BlogPage.css
___________________________________________________________________
Added: svn:eol-style
1381 + native
Index: trunk/extensions/BlogPage/CreateBlogPost.js
@@ -0,0 +1,48 @@
 2+var CreateBlogPost = {
 3+ /**
 4+ * Insert a tag (category) from the category cloud into the inputbox below
 5+ * it on Special:CreateBlogPost
 6+ *
 7+ * @param tagname String: category name
 8+ * @param tagnumber Integer
 9+ */
 10+ insertTag: function( tagname, tagnumber ) {
 11+ document.getElementById( 'tag-' + tagnumber ).style.color = '#CCCCCC';
 12+ document.getElementById( 'tag-' + tagnumber ).innerHTML = tagname;
 13+ // Funny...if you move this getElementById call into a variable and use
 14+ // that variable here, this won't work as intended
 15+ document.getElementById( 'pageCtg' ).value +=
 16+ ( ( document.getElementById( 'pageCtg' ).value ) ? ', ' : '' ) +
 17+ tagname;
 18+ },
 19+
 20+ /**
 21+ * Check that the user has given a title for the blog post and has supplied
 22+ * some content; then check the existence of the title and notify the user
 23+ * if there's already a blog post with the same name as their blog post.
 24+ */
 25+ performChecks: function() {
 26+ // In PHP, we need to use $wgRequest->getVal( 'title2' ); 'title'
 27+ // contains the current special page's name instead of the blog post
 28+ // name
 29+ var title = document.getElementById( 'title' ).value;
 30+ if ( !title || title == '' ) {
 31+ alert( _BLOG_NEEDS_TITLE );
 32+ return '';
 33+ }
 34+ var pageBody = document.getElementById( 'pageBody' ).value;
 35+ if ( !pageBody || pageBody == '' ) {
 36+ alert( _BLOG_NEEDS_CONTENT );
 37+ return '';
 38+ }
 39+
 40+ sajax_request_type = 'POST';
 41+ sajax_do_call( 'SpecialCreateBlogPost::checkTitleExistence', [ title ], function( r ) {
 42+ if( r.responseText.indexOf( 'OK' ) >= 0 ) {
 43+ document.editform.submit();
 44+ } else {
 45+ alert( _BLOG_PAGE_EXISTS );
 46+ }
 47+ });
 48+ }
 49+};
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/CreateBlogPost.js
___________________________________________________________________
Added: svn:eol-style
150 + native
Index: trunk/extensions/BlogPage/Blog.php
@@ -0,0 +1,142 @@
 2+<?php
 3+/**
 4+ * BlogPage -- introduces a new namespace, NS_BLOG (numeric index is 500 by
 5+ * default) and some special handling for the pages in this namespace
 6+ *
 7+ * @file
 8+ * @ingroup Extensions
 9+ * @version 2.0
 10+ * @author David Pean <david.pean@gmail.com>
 11+ * @author Jack Phoenix <jack@countervandalism.net>
 12+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 13+ * @link http://www.mediawiki.org/wiki/Extension:BlogPage Documentation
 14+ */
 15+if ( !defined( 'MEDIAWIKI' ) ) {
 16+ die();
 17+}
 18+
 19+// Extension credits that will show up on Special:Version
 20+$wgExtensionCredits['other'][] = array(
 21+ 'name' => 'BlogPage',
 22+ 'version' => '2.0',
 23+ 'author' => array( 'David Pean', 'Jack Phoenix' ),
 24+ 'description' => 'Blogging system with commenting and voting features, ' .
 25+ '[[Special:CreateBlogPost|a special page to create blog posts]] and ' .
 26+ '[[Special:ArticlesHome|a special page to list blog posts]]',
 27+ 'url' => 'http://www.mediawiki.org/wiki/Extension:BlogPage',
 28+);
 29+
 30+// Define the namespace constants
 31+define( 'NS_BLOG', 500 );
 32+define( 'NS_BLOG_TALK', 501 );
 33+
 34+// ResourceLoader support for MediaWiki 1.17+
 35+$blogResourceTemplate = array(
 36+ 'localBasePath' => dirname( __FILE__ ),
 37+ 'remoteExtPath' => 'BlogPage'
 38+);
 39+
 40+// Main module, used on *all* blog pages (see the hooks file)
 41+$wgResourceModules['ext.blogPage'] = $blogResourceTemplate + array(
 42+ 'styles' => 'BlogPage.css'
 43+);
 44+
 45+// Used on Special:ArticlesHome & Special:ArticleLists
 46+$wgResourceModules['ext.blogPage.articlesHome'] = $blogResourceTemplate + array(
 47+ 'styles' => 'ArticlesHome.css'
 48+);
 49+
 50+// Used on Special:CreateBlogPost
 51+$wgResourceModules['ext.blogPage.create'] = $blogResourceTemplate + array(
 52+ 'styles' => 'CreateBlogPost.css',
 53+ 'scripts' => 'CreateBlogPost.js',
 54+ 'messages' => array(
 55+ 'blog-js-create-error-need-content', 'blog-js-create-error-need-title',
 56+ 'blog-js-create-error-page-exists'
 57+ ),
 58+ 'position' => 'top' // available since r85616
 59+);
 60+
 61+// Default setup for displaying sections
 62+$wgBlogPageDisplay = array(
 63+ // Output the left-hand column? This column contains the list of authors,
 64+ // recent editors (if enabled), recent voters (if enabled), embed widget
 65+ // (if enabled) and left-side advertisement (if enabled).
 66+ 'leftcolumn' => true,
 67+ // Output the right-hand column? This column contains the list of popular
 68+ // blog articles (if enabled), in the news section (if enabled), comments
 69+ // of the day (if enabled), a random casual game (if enabled) and a list of
 70+ // new blog articles.
 71+ 'rightcolumn' => true,
 72+ // Display the box that contains some information about the author of the
 73+ // blog post?
 74+ 'author' => true,
 75+ // Display some (three, to be exact) other blog articles written by the
 76+ // same user?
 77+ 'author_articles' => true,
 78+ // Display a list of people (complete with their avatars) who recently
 79+ // edited this blog post?
 80+ 'recent_editors' => true,
 81+ // Display a list of people (complete with their avatars) who recently
 82+ // voted for this blog post?
 83+ 'recent_voters' => true,
 84+ // Show an advertisement in the left-hand column?
 85+ 'left_ad' => false,
 86+ // Show a listing of the most popular blog posts?
 87+ 'popular_articles' => true,
 88+ // Should we display some random news items from [[MediaWiki:Inthenews]]?
 89+ 'in_the_news' => true,
 90+ // Show comments of the day (comments with the most votes) in the sidebar
 91+ // on a blog post page?
 92+ 'comments_of_day' => true,
 93+ // Display a random casual game (picture game, poll or quiz)?
 94+ // Requires the RandomGameUnit extension.
 95+ 'games' => true,
 96+ // Show a listing of the newest blog posts in blog pages?
 97+ 'new_articles' => true,
 98+ // Display the widget that allows you to embed the blog post on another
 99+ // site? Off by default since it requires the ContentWidget extension,
 100+ // which is currently ArmchairGM-specific.
 101+ 'embed_widget' => false
 102+);
 103+
 104+// Set up everything
 105+$dir = dirname( __FILE__ ) . '/';
 106+$wgExtensionMessagesFiles['Blog'] = $dir . 'Blog.i18n.php';
 107+$wgExtensionAliasesFiles['Blog'] = $dir . 'Blog.alias.php';
 108+// Namespace translations
 109+$wgExtensionMessagesFiles['BlogNamespaces'] = $dir . 'Blog.namespaces.php';
 110+
 111+// Autoload the class which is used when rendering pages in the NS_BLOG NS
 112+$wgAutoloadClasses['BlogPage'] = $dir . 'BlogPage.php';
 113+
 114+// Special pages
 115+$wgAutoloadClasses['ArticlesHome'] = $dir . 'SpecialArticlesHome.php';
 116+$wgAutoloadClasses['ArticleLists'] = $dir . 'SpecialArticleLists.php';
 117+$wgSpecialPages['ArticlesHome'] = 'ArticlesHome';
 118+$wgSpecialPages['ArticleLists'] = 'ArticleLists';
 119+
 120+// Special page for creating new blog posts + yet another copy of TagCloud class
 121+$wgAutoloadClasses['BlogTagCloud'] = $dir . 'TagCloudClass.php';
 122+$wgAutoloadClasses['SpecialCreateBlogPost'] = $dir . 'SpecialCreateBlogPost.php';
 123+$wgSpecialPages['CreateBlogPost'] = 'SpecialCreateBlogPost';
 124+
 125+$wgAjaxExportList[] = 'SpecialCreateBlogPost::checkTitleExistence';
 126+
 127+// New user right, required to create new blog posts via the new special page
 128+$wgAvailableRights[] = 'createblogpost';
 129+$wgGroupPermissions['*']['createblogpost'] = false;
 130+$wgGroupPermissions['user']['createblogpost'] = true;
 131+
 132+// Hooked functions
 133+$wgAutoloadClasses['BlogHooks'] = $dir . 'BlogHooks.php';
 134+
 135+$wgHooks['ArticleFromTitle'][] = 'BlogHooks::blogFromTitle';
 136+$wgHooks['ArticleSaveComplete'][] = 'BlogHooks::updateCreatedOpinionsCount';
 137+$wgHooks['ArticleSave'][] = 'BlogHooks::updateCreatedOpinionsCount';
 138+$wgHooks['AlternateEdit'][] = 'BlogHooks::allowShowEditBlogPage';
 139+$wgHooks['CanonicalNamespaces'][] = 'BlogHooks::onCanonicalNamespaces';
 140+// UserProfile integration
 141+$wgHooks['UserProfileRightSideAfterActivity'][] = 'BlogHooks::getArticles';
 142+// Show blogs in profiles; this needs to be defined to prevent "undefined index" notices
 143+$wgUserProfileDisplay['articles'] = true;
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/Blog.php
___________________________________________________________________
Added: svn:eol-style
1144 + native
Index: trunk/extensions/BlogPage/SpecialArticleLists.php
@@ -0,0 +1,138 @@
 2+<?php
 3+/**
 4+ * A special page that displays the 25 most recent blog posts.
 5+ *
 6+ * @file
 7+ * @ingroup Extensions
 8+ */
 9+class ArticleLists extends IncludableSpecialPage {
 10+
 11+ /**
 12+ * Constructor -- set up the new special page
 13+ */
 14+ public function __construct() {
 15+ parent::__construct( 'ArticleLists' );
 16+ }
 17+
 18+ /**
 19+ * Show the new special page
 20+ *
 21+ * @param $limit Integer: show this many entries (LIMIT for SQL)
 22+ */
 23+ public function execute( $limit ) {
 24+ global $wgMemc, $wgOut, $wgScriptPath;
 25+
 26+ $wgOut->setPageTitle( wfMsg( 'ah-new-articles' ) );
 27+
 28+ if ( empty( $limit ) ) {
 29+ $limit = 25;
 30+ } elseif ( !empty( $limit ) ) {
 31+ $limit = intval( $limit );
 32+ }
 33+
 34+ // Add some CSS for ListPages
 35+ // @todo FIXME: this should be loaded when including the special page,
 36+ // too, but if ( $this->including() ) does nothing, prolly because of
 37+ // the parser cache
 38+ if ( defined( 'MW_SUPPORTS_RESOURCE_MODULES' ) ) {
 39+ $wgOut->addModules( 'ext.blogPage.articlesHome' );
 40+ } else {
 41+ $wgOut->addExtensionStyle( $wgScriptPath . '/extensions/BlogPage/ArticlesHome.css' );
 42+ }
 43+
 44+ $imgPath = $wgScriptPath . '/extensions/BlogPage/images/';
 45+
 46+ $output = '<div class="left-articles">';
 47+ if ( !$this->including() ) {
 48+ $output .= '<h2>' . wfMsg( 'ah-new-articles' ) . '</h2>';
 49+ }
 50+
 51+ // Try cache first
 52+ $key = wfMemcKey( 'blog', 'new', 'twentyfive' );
 53+ $data = $wgMemc->get( $key );
 54+
 55+ if( $data != '' ) {
 56+ wfDebugLog( 'BlogPage', 'Got new articles in ArticleLists from cache' );
 57+ $newBlogPosts = $data;
 58+ } else {
 59+ wfDebugLog( 'BlogPage', 'Got new articles in ArticleLists from DB' );
 60+ $dbr = wfGetDB( DB_SLAVE );
 61+ // Code sporked from Rob Church's NewestPages extension
 62+ // You rock, dude!
 63+ $res = $dbr->select(
 64+ 'page',
 65+ array(
 66+ 'page_namespace', 'page_title', 'page_is_redirect',
 67+ 'page_id'
 68+ ),
 69+ array( 'page_namespace' => NS_BLOG, 'page_is_redirect' => 0 ),
 70+ __METHOD__,
 71+ array( 'ORDER BY' => 'page_id DESC', 'LIMIT' => $limit )
 72+ );
 73+
 74+ $newBlogPosts = array();
 75+ foreach ( $res as $row ) {
 76+ $newBlogPosts[] = array(
 77+ 'title' => $row->page_title,
 78+ 'ns' => $row->page_namespace,
 79+ 'id' => $row->page_id
 80+ );
 81+ }
 82+
 83+ // Cache in memcached for 15 minutes
 84+ $wgMemc->set( $key, $newBlogPosts, 60 * 15 );
 85+ }
 86+
 87+ $output .= '<div class="listpages-container">' . "\n";
 88+ if ( empty( $newBlogPosts ) ) {
 89+ $output .= wfMsg( 'ah-no-results' );
 90+ } else {
 91+ foreach( $newBlogPosts as $newBlogPost ) {
 92+ $titleObj = Title::makeTitle( NS_BLOG, $newBlogPost['title'] );
 93+ $output .= "\t\t\t\t" . '<div class="listpages-item">';
 94+ $pageImage = BlogPage::getPageImage( $newBlogPost['id'] );
 95+ if( $pageImage ) {
 96+ // Load MediaWiki image object to get thumbnail tag
 97+ $img = wfFindFile( $pageImage );
 98+ $imgTag = '';
 99+ if ( is_object( $img ) ) {
 100+ $thumb = $img->getThumbnail( 65, 0, true );
 101+ $imgTag = $thumb->toHtml();
 102+ }
 103+
 104+ $output .= "<div class=\"listpages-image\">{$imgTag}</div>\n";
 105+ }
 106+ $output .= '<a href="' . $titleObj->escapeFullURL() . '">' .
 107+ $titleObj->getText() .
 108+ "</a>
 109+ <div class=\"listpages-blurb\">\n" .
 110+ BlogPage::getBlurb(
 111+ $newBlogPost['title'],
 112+ $newBlogPost['ns'],
 113+ 300
 114+ ) .
 115+ '</div><!-- .listpages-blurb -->
 116+ <div class="listpages-stats">' . "\n";
 117+ $output .= "<img src=\"{$imgPath}voteIcon.gif\" alt=\"\" border=\"0\" /> " .
 118+ wfMsgExt(
 119+ 'blog-author-votes',
 120+ 'parsemag',
 121+ BlogPage::getVotesForPage( $newBlogPost['id'] )
 122+ );
 123+ $output .= " <img src=\"{$imgPath}comment.gif\" alt=\"\" border=\"0\" /> " .
 124+ wfMsgExt(
 125+ 'blog-author-comments',
 126+ 'parsemag',
 127+ BlogPage::getCommentsForPage( $newBlogPost['id'] )
 128+ ) . '</div><!-- . listpages-stats -->
 129+ </div><!-- .listpages-item -->
 130+ <div class="cleared"></div>' . "\n";
 131+ }
 132+ }
 133+ $output .= '</div>' . "\n"; // .listpages-container
 134+ $output .= '</div>' . "\n"; // .left-articles
 135+
 136+ $wgOut->addHTML( $output );
 137+ }
 138+
 139+}
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/SpecialArticleLists.php
___________________________________________________________________
Added: svn:eol-style
1140 + native
Index: trunk/extensions/BlogPage/BlogHooks.php
@@ -0,0 +1,399 @@
 2+<?php
 3+/**
 4+ * All BlogPage's hooked functions. These were previously scattered all over
 5+ * the place in various files.
 6+ *
 7+ * @file
 8+ */
 9+class BlogHooks {
 10+
 11+ /**
 12+ * Calls BlogPage instead of standard Article for pages in the NS_BLOG
 13+ * namespace.
 14+ *
 15+ * @param $title Object: instance of Title
 16+ * @param $article Object: instance of Article that we convert into a BlogPage
 17+ * @return Boolean: true
 18+ */
 19+ public static function blogFromTitle( &$title, &$article ) {
 20+ global $wgRequest, $wgOut, $wgParser, $wgHooks, $wgScriptPath;
 21+ global $wgSupressPageTitle, $wgSupressSubTitle, $wgSupressPageCategories;
 22+
 23+ if ( $title->getNamespace() == NS_BLOG ) {
 24+ if( !$wgRequest->getVal( 'action' ) ) {
 25+ $wgSupressPageTitle = true;
 26+ }
 27+
 28+ $wgSupressSubTitle = true;
 29+ $wgSupressPageCategories = true;
 30+
 31+ // This will suppress category links in SkinTemplate-based skins
 32+ $wgHooks['SkinTemplateOutputPageBeforeExec'][] = function( $sk, $tpl ) {
 33+ $tpl->set( 'catlinks', '' );
 34+ return true;
 35+ };
 36+
 37+ $wgOut->enableClientCache( false );
 38+ $wgParser->disableCache();
 39+
 40+ // Add CSS
 41+ if ( defined( 'MW_SUPPORTS_RESOURCE_MODULES' ) ) {
 42+ $wgOut->addModules( 'ext.blogPage' );
 43+ } else {
 44+ $wgOut->addExtensionStyle( $wgScriptPath . '/extensions/BlogPage/BlogPage.css' );
 45+ }
 46+
 47+ // This originally used $wgTitle but I saw no point in that, so I
 48+ // changed that as per Chad et al.
 49+ $article = new BlogPage( $title );
 50+ }
 51+
 52+ return true;
 53+ }
 54+
 55+ /**
 56+ * Checks that the user is logged is, is not blocked via Special:Block and has
 57+ * the 'edit' user right when they're trying to edit a page in the NS_BLOG NS.
 58+ *
 59+ * @param $editPage Object: instance of EditPage
 60+ * @return Boolean: true if the user should be allowed to continue, else false
 61+ */
 62+ public static function allowShowEditBlogPage( $editPage ) {
 63+ global $wgOut, $wgUser;
 64+ if( $editPage->mTitle->getNamespace() == NS_BLOG ) {
 65+ if( $wgUser->isAnon() ) { // anons can't edit blog pages
 66+ if( !$editPage->mTitle->exists() ) {
 67+ $wgOut->addWikiMsg( 'blog-login' );
 68+ } else {
 69+ $wgOut->addWikiMsg( 'blog-login-edit' );
 70+ }
 71+ return false;
 72+ }
 73+
 74+ if ( !$wgUser->isAllowed( 'edit' ) || $wgUser->isBlocked() ) {
 75+ $wgOut->addHTML( wfMsg( 'blog-permission-required' ) );
 76+ return false;
 77+ }
 78+ }
 79+ return true;
 80+ }
 81+
 82+ /**
 83+ * This function was originally in the UserStats directory, in the file
 84+ * CreatedOpinionsCount.php.
 85+ * This function here updates the stats_opinions_created column in the
 86+ * user_stats table every time the user creates a new blog post.
 87+ *
 88+ * This is hooked into two separate hooks (todo: find out why), ArticleSave
 89+ * and ArticleSaveComplete. Their arguments are mostly the same and both
 90+ * have $article as the first argument.
 91+ *
 92+ * @param $article Object: Article object representing the page that was/is
 93+ * (being) saved
 94+ * @return Boolean: true
 95+ */
 96+ public static function updateCreatedOpinionsCount( &$article ) {
 97+ global $wgOut, $wgUser;
 98+
 99+ $aid = $article->getTitle()->getArticleID();
 100+ // Shortcut, in order not to perform stupid queries (cl_from = 0...)
 101+ if ( $aid == 0 ) {
 102+ return true;
 103+ }
 104+
 105+ $dbr = wfGetDB( DB_SLAVE );
 106+ $res = $dbr->select(
 107+ 'categorylinks',
 108+ 'cl_to',
 109+ // Chad was right, $wgTitle is pure satan; this used to use that
 110+ // and as a result this was completely busted
 111+ array( 'cl_from' => $aid ),
 112+ __METHOD__
 113+ );
 114+
 115+ foreach ( $res as $row ) {
 116+ $ctg = Title::makeTitle( NS_CATEGORY, $row->cl_to );
 117+ $ctgname = $ctg->getText();
 118+ $blogCat = wfMsgForContent( 'blog-category' );
 119+ $userBlogCat = wfMsgForContent( 'blog-by-user-category', $blogCat );
 120+
 121+ if( strpos( $ctgname, $userBlogCat ) !== false ) {
 122+ $user_name = trim( str_replace( $userBlogCat, '', $ctgname ) );
 123+ $u = User::idFromName( $user_name );
 124+
 125+ if( $u ) {
 126+ $stats = new UserStatsTrack( $u, $user_name );
 127+ // Copied from UserStatsTrack::updateCreatedOpinionsCount()
 128+ // Throughout this code, we could use $u and $user_name
 129+ // instead of $stats->user_id and $stats->user_name but
 130+ // there's no point in doing that because we have to call
 131+ // clearCache() in any case
 132+ if ( !$wgUser->isAnon() && $stats->user_id ) {
 133+ $ctg = $userBlogCat . ' '. $stats->user_name;
 134+ $parser = new Parser();
 135+ $ctgTitle = Title::newFromText(
 136+ $parser->preprocess(
 137+ trim( $ctg ),
 138+ $wgOut->parserOptions()
 139+ )
 140+ );
 141+ $ctgTitle = $ctgTitle->getDBkey();
 142+ $dbw = wfGetDB( DB_MASTER );
 143+
 144+ $opinions = $dbw->select(
 145+ array( 'page', 'categorylinks' ),
 146+ array( 'COUNT(*) AS CreatedOpinions' ),
 147+ array(
 148+ 'cl_to' => $ctgTitle,
 149+ 'page_namespace' => NS_BLOG // paranoia
 150+ ),
 151+ __METHOD__,
 152+ array(),
 153+ array(
 154+ 'categorylinks' => array(
 155+ 'INNER JOIN',
 156+ 'page_id = cl_from'
 157+ )
 158+ )
 159+ );
 160+
 161+ // Please die in a fire, PHP.
 162+ // selectField() would be ideal above but it returns
 163+ // insane results (over 300 when the real count is
 164+ // barely 10) so we have to fuck around with a
 165+ // foreach() loop that we don't even need in theory
 166+ // just because PHP is...PHP.
 167+ $opinionsCreated = 0;
 168+ foreach ( $opinions as $opinion ) {
 169+ $opinionsCreated = $opinion->CreatedOpinions;
 170+ }
 171+
 172+ $res = $dbw->update(
 173+ 'user_stats',
 174+ array( 'stats_opinions_created' => $opinionsCreated ),
 175+ array( 'stats_user_id' => $stats->user_id ),
 176+ __METHOD__
 177+ );
 178+
 179+ $stats->clearCache();
 180+ }
 181+ }
 182+ }
 183+ }
 184+
 185+ return true;
 186+ }
 187+
 188+ /**
 189+ * Show a list of this user's blog articles in their user profile page.
 190+ *
 191+ * @param $userProfile Object: instance of UserProfilePage
 192+ * @return Boolean: true
 193+ */
 194+ public static function getArticles( $userProfile ) {
 195+ global $wgUserProfileDisplay, $wgMemc, $wgOut;
 196+
 197+ if ( !$wgUserProfileDisplay['articles'] ) {
 198+ return '';
 199+ }
 200+
 201+ $user_name = $userProfile->user_name;
 202+ $output = '';
 203+
 204+ // Try cache first
 205+ $key = wfMemcKey( 'user', 'profile', 'articles', $userProfile->user_id );
 206+ $data = $wgMemc->get( $key );
 207+ $articles = array();
 208+
 209+ if( $data != '' ) {
 210+ wfDebugLog(
 211+ 'BlogPage',
 212+ "Got UserProfile articles for user {$user_name} from cache\n"
 213+ );
 214+ $articles = $data;
 215+ } else {
 216+ wfDebugLog(
 217+ 'BlogPage',
 218+ "Got UserProfile articles for user {$user_name} from DB\n"
 219+ );
 220+ $categoryTitle = Title::newFromText(
 221+ wfMsgForContent(
 222+ 'blog-by-user-category',
 223+ wfMsgForContent( 'blog-category' )
 224+ ) . " {$user_name}"
 225+ );
 226+
 227+ $dbr = wfGetDB( DB_SLAVE );
 228+ /**
 229+ * I changed the original query a bit, since it wasn't returning
 230+ * what it should've.
 231+ * I added the DISTINCT to prevent one page being listed five times
 232+ * and added the page_namespace to the WHERE clause to get only
 233+ * blog pages and the cl_from = page_id to the WHERE clause so that
 234+ * the cl_to stuff actually, y'know, works :)
 235+ */
 236+ $res = $dbr->select(
 237+ array( 'page', 'categorylinks' ),
 238+ array( 'DISTINCT page_id', 'page_title', 'page_namespace' ),
 239+ /* WHERE */array(
 240+ 'cl_from = page_id',
 241+ 'cl_to' => array( $categoryTitle->getDBkey() ),
 242+ 'page_namespace' => NS_BLOG
 243+ ),
 244+ __METHOD__,
 245+ array( 'ORDER BY' => 'page_id DESC', 'LIMIT' => 5 )
 246+ );
 247+
 248+ foreach ( $res as $row ) {
 249+ $articles[] = array(
 250+ 'page_title' => $row->page_title,
 251+ 'page_namespace' => $row->page_namespace,
 252+ 'page_id' => $row->page_id
 253+ );
 254+ }
 255+
 256+ $wgMemc->set( $key, $articles, 60 );
 257+ }
 258+
 259+ // Load opinion count via user stats;
 260+ $stats = new UserStats( $userProfile->user_id, $user_name );
 261+ $stats_data = $stats->getUserStats();
 262+ $articleCount = $stats_data['opinions_created'];
 263+
 264+ $articleLink = Title::makeTitle(
 265+ NS_CATEGORY,
 266+ wfMsgForContent(
 267+ 'blog-by-user-category',
 268+ wfMsgForContent( 'blog-category' )
 269+ ) . " {$user_name}"
 270+ );
 271+
 272+ if ( count( $articles ) > 0 ) {
 273+ $output .= '<div class="user-section-heading">
 274+ <div class="user-section-title">' .
 275+ wfMsg( 'blog-user-articles-title' ) .
 276+ '</div>
 277+ <div class="user-section-actions">
 278+ <div class="action-right">';
 279+ if( $articleCount > 5 ) {
 280+ $output .= '<a href="' . $articleLink->escapeFullURL() .
 281+ '" rel="nofollow">' . wfMsg( 'user-view-all' ) . '</a>';
 282+ }
 283+ $output .= '</div>
 284+ <div class="action-left">' . wfMsgExt(
 285+ 'user-count-separator',
 286+ 'parsemag',
 287+ count( $articles ),
 288+ $articleCount
 289+ ) . '</div>
 290+ <div class="cleared"></div>
 291+ </div>
 292+ </div>
 293+ <div class="cleared"></div>
 294+ <div class="user-articles-container">';
 295+
 296+ $x = 1;
 297+
 298+ foreach( $articles as $article ) {
 299+ $articleTitle = Title::makeTitle(
 300+ $article['page_namespace'],
 301+ $article['page_title']
 302+ );
 303+ $voteCount = BlogPage::getVotesForPage( $article['page_id'] );
 304+ $commentCount = BlogPage::getCommentsForPage( $article['page_id'] );
 305+
 306+ if ( $x == 1 ) {
 307+ $divClass = 'article-item-top';
 308+ } else {
 309+ $divClass = 'article-item';
 310+ }
 311+ $output .= '<div class="' . $divClass . "\">
 312+ <div class=\"number-of-votes\">
 313+ <div class=\"vote-number\">{$voteCount}</div>
 314+ <div class=\"vote-text\">" .
 315+ wfMsgExt(
 316+ 'blog-user-articles-votes',
 317+ 'parsemag',
 318+ $voteCount
 319+ ) .
 320+ '</div>
 321+ </div>
 322+ <div class="article-title">
 323+ <a href="' . $articleTitle->escapeFullURL() .
 324+ "\">{$articleTitle->getText()}</a>
 325+ <span class=\"item-small\">" .
 326+ wfMsgExt(
 327+ 'blog-user-article-comment',
 328+ 'parsemag',
 329+ $commentCount
 330+ ) . '</span>
 331+ </div>
 332+ <div class="cleared"></div>
 333+ </div>';
 334+
 335+ $x++;
 336+ }
 337+
 338+ $output .= '</div>';
 339+ }
 340+
 341+ $wgOut->addHTML( $output );
 342+
 343+ return true;
 344+ }
 345+
 346+ /**
 347+ * Register the canonical names for our namespace and its talkspace.
 348+ *
 349+ * @param $list Array: array of namespace numbers with corresponding
 350+ * canonical names
 351+ * @return Boolean: true
 352+ */
 353+ public static function onCanonicalNamespaces( &$list ) {
 354+ $list[NS_BLOG] = 'Blog';
 355+ $list[NS_BLOG_TALK] = 'Blog_talk';
 356+ return true;
 357+ }
 358+
 359+ /* optimization :)
 360+ public static function updateFacebookProfile() {
 361+ global $wgUser, $IP, $wgTitle, $wgServer, $wgSitename;
 362+
 363+ // Check if the current user has the app installed
 364+ $dbr = wfGetDB( DB_SLAVE );
 365+ $s = $dbr->selectRow(
 366+ 'fb_link_view_opinions',
 367+ array( 'fb_user_id', 'fb_user_session_key' ),
 368+ array( 'fb_user_id_wikia' => $wgUser->getID() ),
 369+ __METHOD__
 370+ );
 371+
 372+ if ( $s !== false ) {
 373+ require_once "$IP/extensions/Facebook/appinclude.php";
 374+ $facebook = new Facebook( $appapikey, $appsecret );
 375+ //$facebook->api_client->auth_getSession( 'QR1YVV' );
 376+ //$facebook->api_client->session_key = 'QR1YVV';
 377+
 378+ // Update Facebook profile
 379+ try {
 380+ $facebook->api_client->session_key = $infinite_session_key;
 381+ $facebook->api_client->fbml_refreshRefUrl(
 382+ "http://sports.box8.tpa.wikia-inc.com/index.php?title=Special:FacebookGetOpinions&id={$s->fb_user_id}"
 383+ );
 384+ } catch( exception $ex ) {
 385+ }
 386+
 387+ $feedTitle = '<fb:userlink uid="' . $s->fb_user_id .
 388+ '" /> wrote a new article on <a href="' . $wgServer . '">' .
 389+ $wgSitename . '</a>';
 390+ $feedBody = "<a href=\"{$wgTitle->getFullURL()}\">{$wgTitle->getText()}</a>";
 391+ try{
 392+ $facebook->api_client->feed_publishActionOfUser( $feedTitle, $feedBody );
 393+ } catch( exception $ex ) {
 394+ }
 395+ }
 396+
 397+ return true;
 398+ }
 399+ */
 400+}
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/BlogHooks.php
___________________________________________________________________
Added: svn:eol-style
1401 + native
Index: trunk/extensions/BlogPage/TagCloudClass.php
@@ -0,0 +1,72 @@
 2+<?php
 3+/**
 4+ * @file
 5+ * @copyright Copyright © 2007, Wikia Inc.
 6+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 7+ */
 8+class BlogTagCloud {
 9+ var $tags_min_pts = 8;
 10+ var $tags_max_pts = 32;
 11+ var $tags_highest_count = 0;
 12+ var $tags_size_type = 'pt';
 13+
 14+ public function __construct( $limit = 10 ) {
 15+ $this->limit = $limit;
 16+ $this->initialize();
 17+ }
 18+
 19+ public function initialize() {
 20+ $dbr = wfGetDB( DB_MASTER );
 21+ $res = $dbr->select(
 22+ 'categorylinks',
 23+ array( 'cl_to', 'COUNT(*) AS count' ),
 24+ array(),
 25+ __METHOD__,
 26+ array(
 27+ 'GROUP BY' => 'cl_to',
 28+ 'ORDER BY' => 'count DESC',
 29+ 'LIMIT' => $this->limit
 30+ )
 31+ );
 32+
 33+ $message = trim( wfMsgForContent( 'blog-tagcloud-blacklist' ) );
 34+ $catsExcluded = array();
 35+ // Yes, the strlen() is needed, I dunno why wfEmptyMsg() won't work
 36+ if( !wfEmptyMsg( 'blog-tagcloud-blacklist', $message ) && strlen( $message ) > 0 ) {
 37+ $catsExcluded = explode( "\n* ", wfMsgForContent( 'blog-tagcloud-blacklist' ) );
 38+ }
 39+
 40+ wfSuppressWarnings(); // prevent PHP from bitching about strtotime()
 41+ foreach( $res as $row ) {
 42+ $tag_name = Title::makeTitle( NS_CATEGORY, $row->cl_to );
 43+ $tag_text = $tag_name->getText();
 44+ // Exclude dates and blacklisted categories
 45+ if(
 46+ !in_array( $tag_text, $catsExcluded ) &&
 47+ strtotime( $tag_text ) == ''
 48+ )
 49+ {
 50+ if( $row->count > $this->tags_highest_count ) {
 51+ $this->tags_highest_count = $row->count;
 52+ }
 53+ $this->tags[$tag_text] = array( 'count' => $row->count );
 54+ }
 55+ }
 56+ wfRestoreWarnings();
 57+
 58+ // sort tag array by key (tag name)
 59+ if( $this->tags_highest_count == 0 ) {
 60+ return;
 61+ }
 62+ ksort( $this->tags );
 63+ /* and what if we have _1_ category? like on a new wiki with nteen articles, mhm? */
 64+ if( $this->tags_highest_count == 1 ) {
 65+ $coef = $this->tags_max_pts - $this->tags_min_pts;
 66+ } else {
 67+ $coef = ( $this->tags_max_pts - $this->tags_min_pts ) / ( ( $this->tags_highest_count - 1 ) * 2 );
 68+ }
 69+ foreach( $this->tags as $tag => $att ) {
 70+ $this->tags[$tag]['size'] = $this->tags_min_pts + ( $this->tags[$tag]['count'] - 1 ) * $coef;
 71+ }
 72+ }
 73+}
\ No newline at end of file
Property changes on: trunk/extensions/BlogPage/TagCloudClass.php
___________________________________________________________________
Added: svn:eol-style
174 + native

Status & tagging log