<commit_before><?php
/**
 * Implements Special:PendingReviews, an alternative to Special:Watchlist.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 * @ingroup SpecialPage
 */

/**
 * A special page that lists last changes made to the wiki that a user is
 * watching. Pages are listed in reverse-chronological order or by priority;
 * Priority is determined by how many people have already "reviewed" the 
 * change.
 *
 * @ingroup SpecialPage
 */

class SpecialPendingReviews extends SpecialPage {

	public $mMode;
	protected $header_links = array(
		'watchanalytics-pages-specialpage' => '',
		'watchanalytics-users-specialpage' => 'users',
		'watchanalytics-wikihistory-specialpage'  => 'wikihistory',
	);


	/**
	 * Constructor for Special Page.
	 *
	 * @return null
	 */
	public function __construct() {
		parent::__construct(
			"PendingReviews", // 
			"",  // rights required to view
			true // show in Special:SpecialPages
		);
	}
	
	/**
	 * Main function for generating special page.
	 *
	 * First checks if this request is to clear a notification timestamp for a
	 * particular NS/title. If so, clear the notification then generate a
	 * simple response message and return
	 *
	 * Otherwise generates the special page.
	 *
	 * Generally this special page is only for the current user ($wgUser) to
	 * see their own pending reviews, but by setting the 'user' param in the
	 * query string it is possible to view others' Pending Reviews. FIXME: When
	 * this extension is "released" this function should be limited only to
	 * people with a special right.
	 * 
	 * Useful Title functions:
	 * -----------------------
	 *   getAuthorsBetween
	 *   countAuthorsBetwee
	 *   countRevisionsBetween
	 *   exists
	 *   getEditNotices
	 *   getInternalURL - getLinkURL - getLocalURL
	 *   getFullURL
	 *   getFullText - getPrefixedText
	 *   getLatestRevID
	 *   getLength
	 *   getNextRevisionID
	 *   getNotificationTimestamp
	 *   isDeleted (returns num deleted revs) --AND-- isDeletedQuick (returns bool)
	 *   isNewPage
	 *   isRedirect
	 *
	 * @todo FIXME: break sections out into smaller functions - namely HTML writing (HTML templates?x)
	 * @todo FIXME: need logic for: isRedirect, isDeleted, isNewPage, 
	 * and files, approvals ... other log actions?
	 * @todo FIXME: improve documentation above
	 * @param Parser|null $parser
	 * @return bool
	 */
	function execute( $parser = null ) {
		global $wgOut, $wgUser;

		$this->setHeaders();

		// check if the request is to clear a notification timestamp
		$clearNotifyTitle = $this->getClearNotificationTitle();
		if ( $clearNotifyTitle ) {
			$this->handleClearNotification( $clearNotifyTitle );			
			return true;
		}

		// sets user reviews to be displayed (if different from viewing user)
		$this->setPendingReviewsUser();

		// add pending reviews JS (and CSS, but need to explicitly call it below)
		$wgOut->addModules( 'ext.watchanalytics.pendingreviews' );

		// load styles for watch analytics special pages
		// Note: doing $out->addModules( ... ) instead of the two separate
		// functions causes the CSS to load later, which makes the page styles
		// apply late. This looks bad.
		$wgOut->addModuleStyles( array(
			'ext.watchanalytics.specials',
			'ext.watchanalytics.pendingreviews',
		) );

		// how many reviews to display
		$this->setReviewLimit();
		
		//FIXME: is this using a limit?
		$this->pendingReviewList = PendingReview::getPendingReviewsList( $this->mUser );

		$html = $this->getPageHeader();
		
		$html .= '<table class="pendingreviews-list">';
		$rowCount = 0;
	
		// loop through pending reviews
		foreach ( $this->pendingReviewList as $item ) {
			// if the title exists, then the page exists (and hence it has not
			// been deleted)
			if ( $item->title ) {
				$html .= $this->getStandardChangeRow( $item, $rowCount );		
			}
			// page has been deleted (or moved w/o a redirect)
			else {
				$html .= $this->getDeletedPageRow( $item, $rowCount );
			}
		
			$rowCount++;
			if ( $rowCount >= $this->reviewLimit ) {
				break;
			}
		}//die();
		$html .= '</table>';
		$this->getOutput()->addHTML( $html );<commit_msg>first hack at suggesting pages for people to watch<commit_after><?php
/**
 * Implements Special:PendingReviews, an alternative to Special:Watchlist.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 * @ingroup SpecialPage
 */

/**
 * A special page that lists last changes made to the wiki that a user is
 * watching. Pages are listed in reverse-chronological order or by priority;
 * Priority is determined by how many people have already "reviewed" the 
 * change.
 *
 * @ingroup SpecialPage
 */

class SpecialPendingReviews extends SpecialPage {

	public $mMode;
	protected $header_links = array(
		'watchanalytics-pages-specialpage' => '',
		'watchanalytics-users-specialpage' => 'users',
		'watchanalytics-wikihistory-specialpage'  => 'wikihistory',
	);


	/**
	 * Constructor for Special Page.
	 *
	 * @return null
	 */
	public function __construct() {
		parent::__construct(
			"PendingReviews", // 
			"",  // rights required to view
			true // show in Special:SpecialPages
		);
	}
	
	/**
	 * Main function for generating special page.
	 *
	 * First checks if this request is to clear a notification timestamp for a
	 * particular NS/title. If so, clear the notification then generate a
	 * simple response message and return
	 *
	 * Otherwise generates the special page.
	 *
	 * Generally this special page is only for the current user ($wgUser) to
	 * see their own pending reviews, but by setting the 'user' param in the
	 * query string it is possible to view others' Pending Reviews. FIXME: When
	 * this extension is "released" this function should be limited only to
	 * people with a special right.
	 * 
	 * Useful Title functions:
	 * -----------------------
	 *   getAuthorsBetween
	 *   countAuthorsBetwee
	 *   countRevisionsBetween
	 *   exists
	 *   getEditNotices
	 *   getInternalURL - getLinkURL - getLocalURL
	 *   getFullURL
	 *   getFullText - getPrefixedText
	 *   getLatestRevID
	 *   getLength
	 *   getNextRevisionID
	 *   getNotificationTimestamp
	 *   isDeleted (returns num deleted revs) --AND-- isDeletedQuick (returns bool)
	 *   isNewPage
	 *   isRedirect
	 *
	 * @todo FIXME: break sections out into smaller functions - namely HTML writing (HTML templates?x)
	 * @todo FIXME: need logic for: isRedirect, isDeleted, isNewPage, 
	 * and files, approvals ... other log actions?
	 * @todo FIXME: improve documentation above
	 * @param Parser|null $parser
	 * @return bool
	 */
	function execute( $parser = null ) {
		global $wgOut, $wgUser;

		$this->setHeaders();

		// check if the request is to clear a notification timestamp
		$clearNotifyTitle = $this->getClearNotificationTitle();
		if ( $clearNotifyTitle ) {
			$this->handleClearNotification( $clearNotifyTitle );			
			return true;
		}

		// sets user reviews to be displayed (if different from viewing user)
		$this->setPendingReviewsUser();

		// add pending reviews JS (and CSS, but need to explicitly call it below)
		$wgOut->addModules( 'ext.watchanalytics.pendingreviews' );

		// load styles for watch analytics special pages
		// Note: doing $out->addModules( ... ) instead of the two separate
		// functions causes the CSS to load later, which makes the page styles
		// apply late. This looks bad.
		$wgOut->addModuleStyles( array(
			'ext.watchanalytics.specials',
			'ext.watchanalytics.pendingreviews',
		) );

		// how many reviews to display
		$this->setReviewLimit();
		
		//FIXME: is this using a limit?
		$this->pendingReviewList = PendingReview::getPendingReviewsList( $this->mUser );

		$html = $this->getPageHeader();
		
		$html .= '<table class="pendingreviews-list">';
		$rowCount = 0;
	
		// loop through pending reviews
		foreach ( $this->pendingReviewList as $item ) {
			// if the title exists, then the page exists (and hence it has not
			// been deleted)
			if ( $item->title ) {
				$html .= $this->getStandardChangeRow( $item, $rowCount );		
			}
			// page has been deleted (or moved w/o a redirect)
			else {
				$html .= $this->getDeletedPageRow( $item, $rowCount );
			}
		
			$rowCount++;
			if ( $rowCount >= $this->reviewLimit ) {
				break;
			}
		}//die();
		$html .= '</table>';









		#
		#
		#
		#	WARNING! Heinous code below...here be dragons...
		#
		#	(this code is in development and will be cleaned up after it's functional)
		#
		#
		#





		$dbr = wfGetDB( DB_SLAVE );

		$userId = $this->mUser->getId();

		// SELECT
		// 	p.page_id AS p_id,
		// 	w.wl_title AS p_title
		// FROM page AS p
		// LEFT JOIN watchlist AS w
		// 	ON (
		// 		w.wl_namespace = p.page_namespace
		// 		AND w.wl_title = p.page_title
		// 	)
		// WHERE
		// 	w.wl_user = $userId
		// 	AND p.page_namespace = 0
		$userWatchlist = $dbr->select(
			array(
				'p' => 'page',
				'w' => 'watchlist',
			),
			array(
				'p.page_id AS p_id',
				'w.wl_title AS p_title',
 			),
			"w.wl_user=$userId AND p.page_namespace=0",
			__METHOD__,
			array(), // options
			array(
				'w' => array(
					'LEFT JOIN', 
					'w.wl_namespace = p.page_namespace AND w.wl_title = p.page_title'
				)
			)
		);

		$pagesIds = array();
		$pageTitles = array();
		while ( $row = $userWatchlist->fetchObject() ) {
			$userWatchlistPageIds[] = $row->p_id;
			$userWatchlistPageTitles[] = $row->p_title;
		}

		$ids = $dbr->makeList( $userWatchlistPageIds );
		$titles = $dbr->makeList( $userWatchlistPageTitles );

		// $html .= "<pre>" . print_r( $pageIds, true ) . "</pre>";
		// $html .= "<pre>" . print_r( $titles, true ) . "</pre>";



		// SELECT
		// 	pl.pl_from AS pl_from_id,
		// 	p_to.page_id AS pl_to_id
		// FROM pagelinks AS pl
		// INNER JOIN page AS p_to
		// 	ON (
		// 		pl.pl_namespace = p_to.page_namespace
		// 		AND pl.pl_title = p_to.page_title
		// 	)
		// WHERE
		// 	pl.pl_from IN ( <LIST OF all p_id found above> )
		// 	OR ( pl.pl_namespace = 0 AND pl.pl_title IN ( <LIST OF ALL p_title found above> ) )
		$where = 
			"pl.pl_from IN ($ids) " . 
			" OR ( pl.pl_namespace = 0 AND pl.pl_title IN ($titles) )";


		$linkedPagesResult = $dbr->select(
			array(
				'pl' => 'pagelinks',
				'p_to' => 'page',
			),
			array(
				'pl.pl_from AS pl_from_id',
				'p_to.page_id AS pl_to_id',
 			),
			$where,
			__METHOD__,
			array(), // options
			array(
				'p_to' => array(
					'INNER JOIN', 
					'pl.pl_namespace = p_to.page_namespace AND pl.pl_title = p_to.page_title'
				),
			)
		);
		$linkedPages = array();
		while ( $row = $linkedPagesResult->fetchObject() ) {
			if ( ! isset( $linkedPages[ $row->pl_from_id ] ) ) {
				$linkedPages[ $row->pl_from_id ] = 1;
			}
			else {
				$linkedPages[ $row->pl_from_id ]++;				
			}

			if ( ! isset( $linkedPages[ $row->pl_to_id ] ) ) {
				$linkedPages[ $row->pl_to_id ] = 1;
			}
			else {
				$linkedPages[ $row->pl_to_id ]++;				
			}			
		}

		$linkedPagesToKeep = array();
		$linkedPagesList = array();
		foreach ( $linkedPages as $pageId => $numLinks ) {
			if ( ! in_array( $pageId, $userWatchlistPageIds ) ) {
				$linkedPagesToKeep[ $pageId ] = array( 'num_links' => $numLinks );
				$linkedPagesList[] = $pageId;
			}
		}

		// arsort( $linkedPagesToKeep );
		// $html .= "<h1>linked pages before watch query</h1><pre>" . print_r( $linkedPagesToKeep, true ) . "</pre>";

		// $html .= 
		// 	"linked: " . count( $linkedPages ) 
		// 	. '<br />linked to keep:' . count($linkedPagesToKeep)
		// 	. '<br />watchlist: ' . count($userWatchlistPageIds);

		unset( $linkedPages );

		// arsort( $linkedPagesToKeep );

		$pageWatchQuery = new PageWatchesQuery;
		$queryInfo = $pageWatchQuery->getQueryInfo( 'p.page_id IN (' . $dbr->makeList( $linkedPagesList ) . ')' );
		$queryInfo['options'][ 'ORDER BY' ] = 'num_watches ASC';

		$pageWatchStats = $dbr->select(
			$queryInfo['tables'],
			array(
				'p.page_id AS page_id',
				$pageWatchQuery->sqlNumWatches, // 'SUM( IF(w.wl_title IS NOT NULL, 1, 0) ) AS num_watches'
			),
			$queryInfo['conds'],
			__METHOD__,
			$queryInfo['options'],
			$queryInfo['join_conds']
		);

		// add newly found number of watchers to linkedPagesToKeep...
		while ( $row = $pageWatchStats->fetchObject() ) {
			$linkedPagesToKeep[ $row->page_id ][ 'num_watches' ] = $row->num_watches;
		}

		$watches = array();
		$links = array();
		$sortableLinkedPages = array();
		foreach( $linkedPagesToKeep as $pageId => $pageData ) {
			if ( isset( $pageData[ 'num_watches' ] ) ) {
				$numWatches = $pageData[ 'num_watches' ];
			}
			else {
				$numWatches = 0;
			}

			$sortableLinkedPages[] = array(
				'page_id' => $pageId,
				'num_watches' => $numWatches,
				'num_links' => $pageData[ 'num_links' ],
			);
			$watches[] = $numWatches;
			$links[] = $pageData[ 'num_links' ];
		}
		array_multisort( $watches, SORT_ASC, $links, SORT_DESC, $sortableLinkedPages );



		// $html .= "<h1>watchlist</h1><pre>" . print_r( $userWatchlistPageIds, true ) . "</pre>";
		// $html .= "<h1>linked pages</h1><pre>" . print_r( $sortableLinkedPages, true ) . "</pre>";




		$count = 1;
		$html .= "<h2>This wiki needs your help watching pages</h2>"
			. "<p>Few (if any) people are watching the pages below. They are related to other pages in your watchlist, and it'd be really great if you could help by watching some of them."
			. "<ol>";
		foreach( $sortableLinkedPages as $pageId => $pageInfo ) {

			$suggestedTitle = Title::newFromID( $pageInfo[ 'page_id' ] );
			if ( ! $suggestedTitle // for some reason some pages in the pagelinks table don't exist in either table page or table archive...
				|| $suggestedTitle->getNamespace() !== 0 // skip pages not in the main namespace
				|| $suggestedTitle->isRedirect() ) { // don't need redirects
				continue;
			}

			// $cats = $suggestedTitle->getParentCategories();
			// if (  )

			// action=watch&token=9<PASSWORD>%2B%5C
			$watchLinkURL = $suggestedTitle->getLinkURL( array(
				'action' => 'watch',
				'token' => WatchAction::getWatchToken( $suggestedTitle, $wgUser ),
			) );

			$watchLink = Xml::element(
				'a',
				array( 'href' => $watchLinkURL ),
				'watch' //FIXME: use a message
			);

			$html .= '<li><a href="' . $suggestedTitle->getLinkURL() . '">' 
				. $suggestedTitle->getFullText() . '</a>'
				. ' (<strong>' . $watchLink . '</strong>)'
				// . ' - watches: ' . $pageInfo[ 'num_watches' ] . ', links: ' . $pageInfo[ 'num_links' ]
				. '</li>';
			
			$count++;
			if ( $count > 20 ) {
				break;
			}
		
		}
		$html .= '</ol>';


		$this->getOutput()->addHTML( $html );
{'fraction_non_alphanumeric': 0.1347192563748273, 'fraction_numerical': 0.0031403090064062306, 'mean_word_length': 3.057849133537207, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 129, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1864847', 'n_tokens_mistral': 5810, 'n_tokens_neox': 5046, 'n_words': 1547}