BryanDavis has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/98024


Change subject: Cleanup DAO layer
......................................................................

Cleanup DAO layer

* Inject logger into DAO instances
* Log errors and warnings using logger
* Remove unused methods from Dao\Apply
* Remove string concatenations of user provided data in SQL queries
* Allow PDO connection exception to rise to the application level
* Normalize style of query building in all DAO methods

Bug: 57687
Bug: 57570
Change-Id: I9633cbf5fe7fd1dc9a2c0b31ff20e4209f140be0
---
M src/Wikimania/Scholarship/App.php
M src/Wikimania/Scholarship/Controllers/Admin/User.php
M src/Wikimania/Scholarship/Controllers/Review/Application.php
M src/Wikimania/Scholarship/Controllers/Review/Scores.php
M src/Wikimania/Scholarship/Dao/AbstractDao.php
M src/Wikimania/Scholarship/Dao/Apply.php
M src/Wikimania/Scholarship/Dao/User.php
7 files changed, 426 insertions(+), 385 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/wikimedia/wikimania-scholarships 
refs/changes/24/98024/1

diff --git a/src/Wikimania/Scholarship/App.php 
b/src/Wikimania/Scholarship/App.php
index 22daafb..9268dec 100644
--- a/src/Wikimania/Scholarship/App.php
+++ b/src/Wikimania/Scholarship/App.php
@@ -117,20 +117,20 @@
                $container = $this->slim->container;
 
                $container->singleton( 'userDao', function ( $c ) {
-                       return new \Wikimania\Scholarship\Dao\User();
+                       return new \Wikimania\Scholarship\Dao\User( $c->log );
                });
 
                $container->singleton( 'authManager', function ( $c ) {
-                       return new \Wikimania\Scholarship\AuthManager( 
$c['userDao'] );
+                       return new \Wikimania\Scholarship\AuthManager( 
$c->userDao );
                });
 
                $container->singleton( 'applyDao', function ( $c ) {
                        $uid = $c->authManager->getUserId();
-                       return new \Wikimania\Scholarship\Dao\Apply( $uid );
+                       return new \Wikimania\Scholarship\Dao\Apply( $uid, 
$c->log );
                });
 
                $container->singleton( 'wgLang', function ( $c ) {
-                       return new Lang( $c['settings']['i18n.path'] );
+                       return new Lang( $c->settings['i18n.path'] );
                });
 
                $container->singleton( 'applyForm', function ( $c ) {
diff --git a/src/Wikimania/Scholarship/Controllers/Admin/User.php 
b/src/Wikimania/Scholarship/Controllers/Admin/User.php
index 41eafec..d50ee33 100644
--- a/src/Wikimania/Scholarship/Controllers/Admin/User.php
+++ b/src/Wikimania/Scholarship/Controllers/Admin/User.php
@@ -108,8 +108,11 @@
                                }
 
                        } else {
-                               $this->dao->updateUserInfo( $user, $id );
-                               $this->flash( 'info', 'Changes saved.' );
+                               if ( $this->dao->updateUserInfo( $user, $id ) ) 
{
+                                       $this->flash( 'info', 'Changes saved.' 
);
+                               } else {
+                                       $this->flash( 'erorr', 'Save failed. 
Check logs.' );
+                               }
                        }
 
                } else {
diff --git a/src/Wikimania/Scholarship/Controllers/Review/Application.php 
b/src/Wikimania/Scholarship/Controllers/Review/Application.php
index 514be4e..923bb51 100644
--- a/src/Wikimania/Scholarship/Controllers/Review/Application.php
+++ b/src/Wikimania/Scholarship/Controllers/Review/Application.php
@@ -42,7 +42,7 @@
 
                $id = $this->request->get( 'id' );
                if ( $id === null || $id < 0 ) {
-                       $unreviewed = $this->dao->myUnreviewed( $userId, $phase 
);
+                       $unreviewed = $this->dao->myUnreviewed( $phase );
                        $id = min( $unreviewed );
                }
 
@@ -53,7 +53,6 @@
                $this->view->set( 'phase', $phase );
                $this->view->set( 'id', $id );
                $this->view->set( 'myscorings', $this->dao->myRankings( $id, 
$phase ) );
-               $this->view->set( 'allscorings', $this->dao->allRankings( $id, 
$phase ) );
                $this->view->set( 'reviewers', $this->dao->getReviewers( $id, 
$phase ) );
                $this->view->set( 'nextid', $this->dao->nextApp( $id, $phase ) 
);
                $this->view->set( 'previd', $this->dao->prevApp( $id, $phase ) 
);
@@ -73,17 +72,22 @@
                                'valid', 'onwiki', 'future', 'offwiki', 
'program',
                                'englishAbility' );
 
+                       $success = true;
                        foreach ( $criteria as $c ) {
                                $score = $this->request->post( $c );
                                if ( $score !== null ) {
-                                       $this->dao->insertOrUpdateRanking( $id, 
$c, $score );
+                                       $success &= 
$this->dao->insertOrUpdateRanking( $id, $c, $score );
                                }
                        }
 
                        if ( $this->form->get( 'notes' ) !== null ) {
-                               $this->dao->updateNotes( $id, $this->form->get( 
'notes' ) );
+                               $success &= $this->dao->updateNotes( $id, 
$this->form->get( 'notes' ) );
                        }
-                       $this->flash( 'info', 'Changes saved.' );
+                       if ( $success ) {
+                               $this->flash( 'info', 'Changes saved.' );
+                       } else {
+                               $this->flash( 'error', 'Error(s) saving 
changes. See logs.' );
+                       }
 
                } else {
                        // FIXME: log error(s)
diff --git a/src/Wikimania/Scholarship/Controllers/Review/Scores.php 
b/src/Wikimania/Scholarship/Controllers/Review/Scores.php
index ae24b24..36f42f7 100644
--- a/src/Wikimania/Scholarship/Controllers/Review/Scores.php
+++ b/src/Wikimania/Scholarship/Controllers/Review/Scores.php
@@ -33,11 +33,11 @@
 class Scores extends Controller {
 
        protected function handleGet() {
-               $this->form->expectBool( 'partial' );
+               $this->form->expectInt( 'partial', array( 'required' => true ) 
);
                $this->form->validate( $_GET );
 
                $partial = $this->form->get( 'partial' );
-               $rows = $this->dao->getFinalScoring( $partial );
+               $rows = $this->dao->getP2List( $partial, 'All' );
 
                $this->view->set( 'records', $rows );
                $this->render( 'review/scores.html' );
diff --git a/src/Wikimania/Scholarship/Dao/AbstractDao.php 
b/src/Wikimania/Scholarship/Dao/AbstractDao.php
index a5e42cb..5ac5fe5 100644
--- a/src/Wikimania/Scholarship/Dao/AbstractDao.php
+++ b/src/Wikimania/Scholarship/Dao/AbstractDao.php
@@ -26,6 +26,7 @@
 
 use \PDO;
 use \PDOException;
+use Psr\Log\LoggerInterface;
 
 /**
  * Base class for data access objects.
@@ -40,21 +41,75 @@
         */
        protected $dbh;
 
+       /**
+        * @var LoggerInterface $logger
+        */
+       protected $logger;
 
-       public function __construct() {
-               try {
-                       $this->dbh = new PDO( Config::get( 'db_dsn' ),
-                               Config::get( 'db_user' ), Config::get( 
'db_pass' ),
-                               array(
-                                       PDO::ATTR_ERRMODE => 
PDO::ERRMODE_EXCEPTION,
-                                       PDO::ATTR_PERSISTENT => true, //FIXME: 
good idea?
-                                       PDO::ATTR_DEFAULT_FETCH_MODE => 
PDO::FETCH_ASSOC,
-                               )
-                       );
-               } catch ( PDOException $e ) {
-                       // FIXME: yuck
-                       error_log( __METHOD__ . ": {$e}" );
-                       die( "Failed to connect to database" );
+
+       /**
+        * @param LoggerInterface $logger Log channel
+        */
+       public function __construct( $logger = null ) {
+               $this->logger = $logger ?: new \Psr\Log\NullLogger();
+
+               $this->dbh = new PDO( Config::get( 'db_dsn' ),
+                       Config::get( 'db_user' ), Config::get( 'db_pass' ),
+                       array(
+                               PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+                               PDO::ATTR_PERSISTENT => true, //FIXME: good 
idea?
+                               PDO::ATTR_DEFAULT_FETCH_MODE => 
PDO::FETCH_ASSOC,
+                       )
+               );
+       }
+
+
+       /**
+        * Bind values to a prepared statement.
+        *
+        * If an associative array of values is provided, the data type to use 
when
+        * binding will be inferred by looking for a "<type>_" prefix at the
+        * beginning of the array key. This can come in very handy if you are 
using
+        * parameters in places like LIMIT clauses where binding as a string 
(the
+        * default type for PDO binds) will cause a syntax error.
+        *
+        * @param \PDOStatement $stmt Previously prepared statement
+        * @param array $values Values to bind
+        */
+       protected function bind( $stmt, $values ) {
+               $values = $values ?: array();
+
+               if ( (bool)count( array_filter( array_keys( $values ), 
'is_string' ) ) ) {
+                       // associative array provided
+                       foreach ( $values as $key => $value ) {
+                               // infer bind type from key prefix
+                               list( $prefix, $ignored ) = explode( '_', 
"{$key}_", 2 );
+
+                               $type = \PDO::PARAM_STR;
+                               switch ( $prefix ) {
+                                       case 'int':
+                                               $type = \PDO::PARAM_INT;
+                                               break;
+                                       case 'bool':
+                                               $type = \PDO::PARAM_BOOL;
+                                               break;
+                                       case 'null':
+                                               $type = \PDO::PARAM_NULL;
+                                               break;
+                                       default:
+                                               $type = \PDO::PARAM_STR;
+                               }
+
+                               $stmt->bindValue( $key, $value, $type );
+                       }
+
+               } else {
+                       // vector provided
+                       $idx = 1;
+                       foreach ( $values as $value ) {
+                               $stmt->bindValue( $idx, $value );
+                               $idx++;
+                       }
                }
        }
 
@@ -68,7 +123,8 @@
         */
        protected function fetch( $sql, $params = null ) {
                $stmt = $this->dbh->prepare( $sql );
-               $stmt->execute( $params );
+               $this->bind( $stmt, $params );
+               $stmt->execute();
                return $stmt->fetch();
        }
 
@@ -81,10 +137,13 @@
         * @return array Result rows
         */
        protected function fetchAll( $sql, $params = null ) {
+               $this->logger->debug( $sql, $params ?: array() );
                $stmt = $this->dbh->prepare( $sql );
-               $stmt->execute( $params );
+               $this->bind( $stmt, $params );
+               $stmt->execute();
                return $stmt->fetchAll();
        }
+
 
        /**
         * Prepare and execute an SQL statement and return all results plus the
@@ -104,6 +163,7 @@
                return $ret;
        }
 
+
        /**
         * Prepare and execute an SQL statement in a transaction.
         *
@@ -118,10 +178,15 @@
                        $stmt->execute( $params );
                        $this->dbh->commit();
                        return true;
+
                } catch ( PDOException $e) {
                        $this->dbh->rollback();
-                       //fixme: logging
-                       error_log( __METHOD__ . " [{$sql}]: {$e}" );
+                       $this->logger->error( 'Update failed.', array(
+                               'method' => __METHOD__,
+                               'exception' => $e,
+                               'sql' => $sql,
+                               'params' => $params,
+                       ) );
                        return false;
                }
        }
@@ -142,13 +207,19 @@
                        $rowid = $this->dbh->lastInsertId();
                        $this->dbh->commit();
                        return $rowid;
+
                } catch ( PDOException $e) {
                        $this->dbh->rollback();
-                       // FIXME: logging
-                       error_log( __METHOD__ . " [{$sql}]: {$e}" );
+                       $this->logger->error( 'Insert failed.', array(
+                               'method' => __METHOD__,
+                               'exception' => $e,
+                               'sql' => $sql,
+                               'params' => $params,
+                       ) );
                        return false;
                }
        }
+
 
        /**
         * Construct a where clause.
@@ -158,17 +229,31 @@
         */
        protected static function buildWhere( array $where, $conjunction = 
'AND' ) {
                if ( $where ) {
-                       return 'WHERE ' . implode( " {$conjunction} ", $where ) 
. ' ';
+                       return 'WHERE (' . implode( ") {$conjunction} (", 
$where ) . ') ';
                }
                return '';
        }
 
+
        /**
         * Create a string by joining all arguments with spaces.
+        *
+        * If one or more of the arguments are arrays each element of the array 
will
+        * be included independently.
+        *
         * @return string New string
         */
        protected static function concat( /*varags*/ ) {
-               return implode( ' ', func_get_args() );
+               $args = array();
+               foreach ( func_get_args() as $arg ) {
+                       if ( is_array( $arg ) ) {
+                               $args = array_merge( $args, $arg );
+                       } else {
+                               $args[] = $arg;
+                       }
+               }
+
+               return implode( ' ', $args );
        }
 
 } //end AbstractDao
diff --git a/src/Wikimania/Scholarship/Dao/Apply.php 
b/src/Wikimania/Scholarship/Dao/Apply.php
index 9794b08..61b15fd 100644
--- a/src/Wikimania/Scholarship/Dao/Apply.php
+++ b/src/Wikimania/Scholarship/Dao/Apply.php
@@ -38,10 +38,16 @@
         */
        protected $userid;
 
-       public function __construct( $uid = false ) {
-               parent::__construct();
+
+       /**
+        * @param int|bool $uid Authenticated user
+        * @param LoggerInterface $logger Log channel
+        */
+       public function __construct( $uid = false, $logger = null ) {
+               parent::__construct( $logger );
                $this->userid = $uid;
        }
+
 
        /**
         * Save a new application.
@@ -62,6 +68,7 @@
                return $this->insert( $sql, $answers );
        }
 
+
        /**
         * @param array $params Query parameters
         */
@@ -77,15 +84,18 @@
                $params = array_merge( $defaults, $params );
 
                $where = array();
-               $myid = $this->userid ?: 0;
+               $bindVars = array();
+               $bindVars['int_userid'] = $this->userid ?: 0;
 
                if ( $params['items'] == 'all' ) {
-                       $limit = " ";
-                       $offset = " ";
+                       $limit = '';
+                       $offset = '';
 
                } else {
-                       $limit = " LIMIT {$params['items']} ";
-                       $offset = " OFFSET " . ( (int)$params['page'] * 
$params['items'] );
+                       $bindVars['int_limit'] = (int)$params['items'];
+                       $bindVars['int_offset'] = (int)$params['page'] * 
(int)$params['items'];
+                       $limit = 'LIMIT :int_limit';
+                       $offset = 'OFFSET :int_offset';
                }
 
                $fields = array(
@@ -128,25 +138,14 @@
                        }
                }
 
-               $p1scoreSql = self::concat(
-                       "SELECT scholarship_id, SUM(rank) AS p1score",
-                       "FROM rankings",
-                       "WHERE criterion = 'valid'",
-                       "GROUP BY scholarship_id"
-               );
-
-               $p1countSql = self::concat(
-                       "SELECT scholarship_id, COUNT(rank) AS p1count",
-                       "FROM rankings",
-                       "WHERE criterion = 'valid'",
-                       "GROUP BY scholarship_id"
-               );
+               $p1scoreSql = $this->makeAggregateRankSql( 'valid', 'SUM', 
'p1score' );
+               $p1countSql = $this->makeAggregateRankSql( 'valid', 'COUNT', 
'p1count' );
 
                $mycountSql = self::concat(
                        "SELECT scholarship_id, COUNT(rank) AS mycount",
                        "FROM rankings",
                        "WHERE criterion = 'valid'",
-                       "AND user_id = {$myid}",
+                       "AND user_id = :int_userid",
                        "GROUP BY scholarship_id"
                );
 
@@ -168,126 +167,85 @@
                        "SELECT scholarship_id, COUNT(rank) AS mycount",
                        "FROM rankings",
                        "WHERE criterion <> 'valid'",
-                       "AND user_id = {$myid}",
+                       "AND user_id = :int_userid",
                        "GROUP BY scholarship_id"
                );
+
+               $joins = array(
+                       "LEFT OUTER JOIN iso_countries c ON s.residence = 
c.code",
+                       "LEFT OUTER JOIN ({$p1scoreSql}) r1 ON s.id = 
r1.scholarship_id",
+               );
+
+               $havingExtra = '';
 
                if ( $params['phase'] == 1 ) {
                        $fields[] = 'p1count';
 
-                       $min = (int)$params['min'];
-                       $max = (int)$params['max'];
-
-                       $sql = self::concat(
-                               "SELECT SQL_CALC_FOUND_ROWS", implode( ',', 
$fields ),
-                               "FROM scholarships s",
-                               "LEFT OUTER JOIN ( {$p1scoreSql} ) r1 ON s.id = 
r1.scholarship_id",
-                               "LEFT OUTER JOIN ( {$p1countSql} ) r2 on s.id = 
r2.scholarship_id",
+                       $joins = array_merge( $joins, array(
+                               "LEFT OUTER JOIN ({$p1countSql}) r2 on s.id = 
r2.scholarship_id",
                                "LEFT OUTER JOIN ( {$mycountSql} ) r3 on s.id = 
r3.scholarship_id",
-                               "LEFT OUTER JOIN iso_countries c ON s.residence 
= c.code",
-                               $this->buildWhere( $where ),
-                               "GROUP BY s.id, s.fname, s.lname, s.email, 
s.residence",
-                               "HAVING p1score >= {$min} AND p1score <= {$max} 
AND s.exclude = 0",
-                               "ORDER BY s.id",
-                               $limit,
-                               $offset
-                       );
+                       ) );
+
+                       $havingExtra = "AND p1score >= :int_min AND p1score <= 
:int_max";
+                       $bindVars['int_min'] = (int)$params['min'];
+                       $bindVars['int_max'] = (int)$params['max'];
 
                } else {
                        $fields[] = 'COALESCE(p2score, 0) as p2score';
                        $fields[] = 'COALESCE(nscorers, 0) as nscorers';
 
+                       //FIXME: hardcoded scoring
                        $where[] = 'p1score >= 3';
 
-                       $sql = self::concat(
-                               "SELECT SQL_CALC_FOUND_ROWS", implode( ',', 
$fields ),
-                               "FROM scholarships s",
-                               "LEFT OUTER JOIN ( {$p1scoreSql} ) r1 ON s.id = 
r1.scholarship_id",
-                               "LEFT OUTER JOIN ( {$p2scoreSql} ) r2 ON s.id = 
r2.scholarship_id",
-                               "LEFT OUTER JOIN ( {$nscorersSql} ) r3 ON s.id 
= r3.scholarship_id",
-                               "LEFT OUTER JOIN ( {$mycount2Sql} ) r4 ON s.id 
= r4.scholarship_id",
-                               "LEFT OUTER JOIN iso_countries c ON s.residence 
= c.code",
-                               $this->buildWhere( $where ),
-                               "GROUP BY s.id, s.fname, s.lname, s.email, 
s.residence",
-                               "ORDER BY s.id",
-                               $limit,
-                               $offset
-                       );
+                       $joins = array_merge( $joins, array(
+                               "LEFT OUTER JOIN ({$p2scoreSql}) r2 ON s.id = 
r2.scholarship_id",
+                               "LEFT OUTER JOIN ({$nscorersSql}) r3 ON s.id = 
r3.scholarship_id",
+                               "LEFT OUTER JOIN ({$mycount2Sql}) r4 ON s.id = 
r4.scholarship_id",
+                       ) );
                }
 
-               return $this->fetchAllWithFound( $sql );
+               $sql = self::concat(
+                       "SELECT SQL_CALC_FOUND_ROWS", implode( ',', $fields ),
+                       "FROM scholarships s",
+                       $joins,
+                       self::buildWhere( $where ),
+                       "GROUP BY s.id, s.fname, s.lname, s.email, s.residence",
+                       "HAVING s.exclude = 0", $havingExtra,
+                       "ORDER BY s.id",
+                       $limit,
+                       $offset
+               );
+
+
+               return $this->fetchAllWithFound( $sql, $bindVars );
        } // end gridData
 
-       public function export() {
-               $tables = array( 's' => 'scholarships' );
-               $fields = array( 's.id',
-                       'c.country_name', // residence
-                       'ow.onwiki',
-                       'ofw.offwiki',
-                       'f.future',
-                       'ct.numranks',
-                       's.fname',
-                       's.lname',
-                       's.dob',
-                       's.sex',
-                       's.email',
-                       's.telephone',
-                       's.address',
-                       'c2.country_name', // nationality
-                       's.haspassport',
-                       's.airport',
-                       's.languages',
-                       's.occupation',
-                       's.areaofstudy',
-                       's.wm05',
-                       's.wm06',
-                       's.wm07',
-                       's.wm08',
-                       's.wm09',
-                       's.wm10',
-                       's.wm11',
-                       's.wm12',
-                       's.howheard',
-                       's.why',
-                       's.future',
-                       's.involvement',
-                       's.contribution',
-                       'ea.englishAbility',
-                       's.username',
-                       's.project',
-                       's.projectlangs',
-                       's.wantspartial',
-                       's.canpaydiff',
-                       's.sincere',
-                       's.agreestotravelconditions',
-                       's.willgetvisa',
-                       's.willpayincidentals',
-                       's.notes'
-               );
-               $sql = self::concat(
-                       "SELECT " . implode( ',', $fields ),
-                       "FROM scholarships s",
-                       "LEFT JOIN (select scholarship_id, avg(rank) as onwiki 
from rankings where criterion IN ('onwiki') group by scholarship_id) ow ON 
(ow.scholarship_id = s.id)
-                       LEFT JOIN (select scholarship_id, avg(rank) as offwiki 
from rankings where criterion IN ('offwiki') group by scholarship_id) ofw ON 
(ofw.scholarship_id = s.id)
-                       LEFT JOIN (select scholarship_id, avg(rank) as future 
from rankings where criterion IN ('future') group by scholarship_id) f ON 
(f.scholarship_id = s.id)
-                       LEFT JOIN (select scholarship_id, avg(rank) as 
englishAbility from rankings where criterion IN ('englishAbility') group by 
scholarship_id) ea ON (ea.scholarship_id = s.id)
-                       LEFT JOIN (select scholarship_id, count(rank) as 
numranks from rankings where criterion IN ('future') group by scholarship_id) 
ct ON (ct.scholarship_id  = s.id)
-                       LEFT OUTER JOIN iso_countries c ON s.residence = c.code
-                       LEFT OUTER JOIN iso_countries c2 ON s.nationality = 
c2.code
-                       order by s.id limit 20");
-               return $this->fetchAll( $sql );
-       }
 
        public function myUnreviewed( $phase ) {
-               // FIXME: NOT IN () instead of left join? (count not used)
-               // FIXME: this isn't right, doesn't care about phase
-               $sql = "SELECT s.id FROM scholarships s
-                       LEFT OUTER JOIN (select scholarship_id, count(rank) as 
mycount from rankings WHERE user_id = ? GROUP BY scholarship_id) r4 on s.id = 
r4.scholarship_id
-                       WHERE mycount IS NULL;";
+               if ( $phase == 1 ) {
+                       $crit = 'valid';
+               } else {
+                       $crit = 'future';
+               }
 
-               $res = $this->fetchAll( $sql, array( $this->userid ) );
+               $sql = self::concat(
+                       "SELECT s.id",
+                       "FROM scholarships s",
+                       "WHERE s.id NOT IN (",
+                       "SELECT scholarship_id",
+                       "FROM rankings",
+                       "WHERE user_id = :int_uid",
+                       "AND criterion = :crit)",
+                       "ORDER BY s.id"
+               );
+
+               $res = $this->fetchAll( $sql, array(
+                       'int_uid' => $this->userid,
+                       'crit' => $crit,
+               ) );
                return array_map( function ($row) { return $row['id']; }, $res 
);
        }
+
 
        public function search( $params ) {
                $defaults = array(
@@ -299,29 +257,36 @@
                        'page' => 0,
                );
                $params = array_merge( $defaults, $params );
-               $myid = $this->userid ?: 0;
-
-               $offset = "OFFSET " . ( (int)$params['page'] * $params['items'] 
);
-               $limit = "LIMIT {$params['items']}";
 
                $where = array();
-               $crit = array( ':uid' => $myid );
+               $crit = array(
+                       'int_uid' => $this->userid ?: 0,
+               );
+
+               $limit = "LIMIT :int_limit";
+               $crit['int_limit'] = $params['items'];
+
+               $offset = "OFFSET :int_offset";
+               $crit['int_offset'] = (int)$params['page'] * 
(int)$params['items'];
+
                if ( $params['last'] !== null ) {
                        $where[] = "s.lname = :last";
-                       $crit[':last'] = $params['last'];
+                       $crit['last'] = $params['last'];
                }
                if ( $params['first'] !== null ) {
                        $where[] = "s.fname = :first";
-                       $crit[':first'] = $params['first'];
+                       $crit['first'] = $params['first'];
                }
                if ( $params['residence'] !== null ) {
                        $where[] = "c.country_name = :residence";
-                       $crit[':residence'] = $params['residence'];
+                       $crit['residence'] = $params['residence'];
                }
                if ( $params['region'] !== null ) {
                        $where[] = "c.region = :region";
-                       $crit[':region'] = $params['region'];
+                       $crit['region'] = $params['region'];
                }
+
+               $where[] = "s.exclude = 0";
 
                $fields = array(
                        "s.id",
@@ -331,34 +296,23 @@
                        "s.residence",
                        "s.exclude",
                        "s.sex",
-                       "(year(now()) - year(s.dob)) as age",
-                       "(s.canpaydiff*s.wantspartial) as partial",
+                       "(YEAR(NOW()) - YEAR(s.dob)) AS age",
+                       "(s.canpaydiff * s.wantspartial) AS partial",
                        "c.country_name",
                        "c.region",
-                       "coalesce(p1score, 0) as p1score",
+                       "COALESCE(p1score, 0) AS p1score",
                        "p1count",
                        "mycount",
                );
 
-               $p1scoreSql = self::concat(
-                       "SELECT scholarship_id, SUM(rank) AS p1score",
-                       "FROM rankings",
-                       "WHERE criterion = 'valid'",
-                       "GROUP BY scholarship_id"
-               );
-
-               $p1countSql = self::concat(
-                       "SELECT scholarship_id, COUNT(rank) AS p1count",
-                       "FROM rankings",
-                       "WHERE criterion = 'valid'",
-                       "GROUP BY scholarship_id"
-               );
+               $p1scoreSql = $this->makeAggregateRankSql( 'valid', 'SUM', 
'p1score' );
+               $p1countSql = $this->makeAggregateRankSql( 'valid', 'COUNT', 
'p1count' );
 
                $mycountSql = self::concat(
                        "SELECT scholarship_id, COUNT(rank) AS mycount",
                        "FROM rankings",
                        "WHERE criterion = 'valid'",
-                       "AND user_id = :uid",
+                       "AND user_id = :int_uid",
                        "GROUP BY scholarship_id"
                );
 
@@ -369,9 +323,7 @@
                                "LEFT OUTER JOIN ( {$p1countSql} ) r2 ON s.id = 
r2.scholarship_id",
                                "LEFT OUTER JOIN ( {$mycountSql} ) r3 ON s.id = 
r3.scholarship_id",
                                "LEFT OUTER JOIN iso_countries c ON s.residence 
= c.code",
-                               $this->buildWhere( $where ),
-                               "GROUP BY s.id, s.fname, s.lname, s.email, 
s.residence",
-                               "HAVING p1score >= -2 AND p1score <= 999 AND 
s.exclude = 0",
+                               self::buildWhere( $where ),
                                "ORDER BY s.id",
                                $limit,
                                $offset
@@ -382,27 +334,23 @@
 
 
        public function getScholarship( $id ) {
-               return $this->fetch( 'select *, s.id, s.residence as acountry, 
c.country_name, r.country_name as residence_name from scholarships s
-                       left outer join iso_countries c on s.nationality = 
c.code
-                       left outer join iso_countries r on s.residence = r.code
-                       where s.id = ?', array( $id ) );
+               $sql = self::concat(
+                       'SELECT *, s.id, c.country_name, r.country_name AS 
residence_name',
+                       'FROM scholarships s',
+                       'LEFT OUTER JOIN iso_countries c ON s.nationality = 
c.code',
+                       'LEFT OUTER JOIN iso_countries r ON s.residence = 
r.code',
+                       'WHERE s.id = :int_id'
+               );
+               return $this->fetch( $sql, array( 'int_id' => $id ) );
        }
 
-       public function getNext( $userid, $id, $phase ) {
-               $nextid = $this->getNextId( $userid, $id, $phase );
-               if ( $nextid != false ) {
-                       return $this->getScholarship( $i );
-               }
-               return false;
-       }
 
        /**
         * Find the next unreviewed id after the given id.
         * @return int|bool Next id or false if none available
         */
        public function nextApp( $id, $phase ) {
-               // FIXME: this can probably be done in sql
-               $myapps = $this->myUnreviewed( $this->userid, $phase );
+               $myapps = $this->myUnreviewed( $phase );
                foreach ( $myapps as $app ) {
                        if ( $app > $id ) {
                                return $app;
@@ -411,103 +359,87 @@
                return false;
        }
 
+
        public function prevApp( $id, $phase ) {
-               // FIXME: this can probably be done in sql
-               $myapps = $this->myUnreviewed( $this->userid, $phase );
+               $myapps = $this->myUnreviewed( $phase );
                $prior = false;
                foreach ( $myapps as $app ) {
                        if ( $app >= $id ) {
                                return $prior;
                        }
-                       $prior = $id;
+                       $prior = $app;
                }
                return false;
        }
 
-       public function getNextId( $userid, $id, $phase ) {
-               $myapps = $this->myUnreviewed( $userid, $phase );
-               for ( $i = $id; $i < max( $myapps ); $i++ ) {
-                       if ( in_array( $i, $myapps ) ) {
-                               return $i;
-                       }
-               }
-               return false;
-       }
 
        public function insertOrUpdateRanking( $scholarship_id, $criterion, 
$rank ) {
-               $this->update( self::concat(
+               $sql = self::concat(
                        'INSERT INTO rankings (user_id, scholarship_id, 
criterion, rank)',
-                       'VALUES (?, ?, ?, ?)',
-                       'ON DUPLICATE KEY UPDATE rank = ?, entered_on = now()' 
),
-                       array( $this->userid, $scholarship_id, $criterion, 
$rank, $rank )
+                       'VALUES (:int_uid, :int_sid, :str_crit, :int_rank)',
+                       'ON DUPLICATE KEY UPDATE rank = :int_rank, entered_on = 
now()'
                );
+
+               return $this->update( $sql, array(
+                       'int_uid' => $this->userid,
+                       'int_sid' => $scholarship_id,
+                       'str_crit' => $criterion,
+                       'int_rank' => $rank,
+               ) );
        }
+
 
        public function getReviewers( $id, $phase ) {
-               $where = array( "r.scholarship_id = ?" );
+               $where = array( "r.scholarship_id = :int_sid" );
                if ( $phase == 1 ) {
-                       array_push( $where, "r.criterion IN ('valid')" );
+                       $where[] = "r.criterion = 'valid'";
                } else {
-                       array_push( $where, "r.criterion IN ('future', 
'onwiki', 'offwiki', 'englishAbility')" );
+                       $where[] = "r.criterion = 'future'";
                }
-               $sql = "select distinct(u.username) as username from rankings r 
inner join users u on r.user_id = u.id " . $this->buildWhere( $where ) . " 
order by u.username";
-               return $this->fetchAll( $sql, array( $id ) );
+
+               $sql = self::concat(
+                       "SELECT DISTINCT(u.username) AS username",
+                       "FROM rankings r",
+                       "INNER JOIN users u ON r.user_id = u.id",
+                       self::buildWhere( $where ),
+                       "ORDER BY u.username"
+               );
+               return $this->fetchAll( $sql, array( 'int_sid' => $id ) );
        }
+
 
        public function myRankings( $id, $phase ) {
+               $where = array( "r.scholarship_id = :int_sid" );
                if ( $phase == 1 ) {
-                       $sql = "select r.scholarship_id, u.username, r.rank, 
r.criterion from rankings r inner join users u on r.user_id = u.id where 
r.criterion = 'valid' and u.id = ? AND r.scholarship_id = ?";
+                       $where[] = "r.criterion = 'valid'";
 
-               } else if ( $phase == 2 ) {
-                       $sql = "select r.scholarship_id, u.username, r.rank, 
r.criterion from rankings r inner join users u on r.user_id = u.id where 
r.criterion IN ('onwiki', 'future', 'offwiki', 'program', 'englishAbility') and 
u.id = ? and r.scholarship_id = ? order by r.criterion, u.username, r.rank";
                } else {
-                       return false;
+                       $where[] = "r.criterion <> 'valid'";
                }
-               return $this->fetchAll( $sql, array( $this->userid, $id ) );
-       }
+               $where[] = "u.id = :int_uid";
 
-       public function allRankings( $id, $phase ) {
-               if ( $phase == 1 ) {
-                       $sql = 'select r.scholarship_id, u.username, r.rank, 
r.criterion from rankings r inner join users u on r.user_id = u.id where 
r.criterion = "valid" and r.scholarship_id = ?';
-               } else if ( $phase == 2 ) {
-                       $sql = "select r.scholarship_id, u.username, r.rank, 
r.criterion from rankings r inner join users u on r.user_id = u.id where 
r.criterion IN ('onwiki', 'future', 'offwiki', 'program', 'englishAbility') and 
r.scholarship_id = ? order by r.criterion, u.username, r.rank";
-               } else {
-                       return false;
-               }
-               return $this->fetchAll( $sql, array( $id ) );
-       }
-
-       public function getRankingOfUser( $user_id, $scholarship_id, $criterion 
) {
-               $sql = 'select rank from rankings where user_id = ? and 
scholarship_id = ? and criterion = ?';
-               $ret = $this->fetch( $sql, array( $user_id, $scholarship_id, 
$criterion ) );
-               return ( count( $ret ) > 0 ) ? $ret['rank'] : 0;
-       }
-
-       public function updateNotes( $id, $notes ) {
-               $this->update(
-                       'update scholarships set notes = ? where id = ?',
-                       array( $notes, $id )
+               $sql = self::concat(
+                       "SELECT r.scholarship_id, u.username, r.rank, 
r.criterion",
+                       "FROM rankings r",
+                       "INNER JOIN users u ON r.user_id = u.id",
+                       self::buildWhere( $where ),
+                       "ORDER BY r.criterion, r.rank"
                );
+               return $this->fetchAll( $sql, array(
+                       'int_sid' => $id,
+                       'int_uid' => $this->userid,
+               ) );
        }
 
-       // Phase List
 
-       public function GetPhase1EarlyRejects() {
-               return$this->fetchAll( "select s.id, s.fname, s.lname, s.email, 
s.exclude, coalesce(p1score,0) as p1score from scholarships s
-                       left outer join (select scholarship_id, sum(rank) as 
p1score from rankings where criterion = 'valid' group by scholarship_id) r2 on 
s.id = r2.scholarship_id
-                       group by s.id, s.fname, s.lname, s.email
-                       having p1score < 3 and s.exclude = 0" );
-       }
-
-       public function GetPhase1Success() {
-               // FIXME: hoist and reuse
-               $p1scoreSql = self::concat(
-                       "SELECT scholarship_id, SUM(rank) AS p1score",
-                       "FROM rankings",
-                       "WHERE criterion = 'valid'",
-                       "GROUP BY scholarship_id"
-               );
-
+       /**
+        * Get a list of scholarships at the close of phase 1 screening.
+        *
+        * @param bool $success True to get phase 2 eligable applicants, false 
for
+        * rejects
+        * @return array Query results
+        */
+       protected function getPhase1List( $success ) {
                $fields = array(
                        's.id',
                        's.fname',
@@ -517,53 +449,40 @@
                        'COALESCE(p1score, 0) as p1score',
                );
 
+               $p1scoreSql = $this->makeAggregateRankSql( 'valid', 'SUM', 
'p1score' );
+
+               $op = ( $success ) ? '>=' : '<' ;
+
                return $this->fetchAll( self::concat(
                        "SELECT" , implode( ',', $fields ),
                        "FROM scholarships s",
-                       "LEFT OUTER JOIN ( {$p1scoreSql} ) r2 ON s.id = 
r2.scholarship_id",
+                       "LEFT OUTER JOIN ({$p1scoreSql}) r2 ON s.id = 
r2.scholarship_id",
                        "GROUP BY s.id, s.fname, s.lname, s.email",
-                       "HAVING p1score >= 3 AND s.exclude = 0" ) );
+                       //FIXME: hardcoded score
+                       "HAVING p1score {$op} 3 AND s.exclude = 0"
+               ) );
        }
+
+
+       public function getPhase1EarlyRejects() {
+               return $this->getPhase1List( false );
+       }
+
+
+       public function getPhase1Success() {
+               return $this->getPhase1List( true );
+       }
+
 
        public function getRegionList() {
                $res = $this->fetchAll( "SELECT DISTINCT region FROM 
iso_countries" );
                return array_map( function ($row) { return $row['region']; }, 
$res );
        }
 
-       public function getP2List( $partial, $region ) {
-               //FIXME make this prettier
-               $sql = "SELECT s.id, s.fname, s.lname, s.email, s.residence, 
s.exclude, s.sex, year(now())-year(s.dob) as age, (s.canpaydiff*s.wantspartial) 
as partial, c.country_name, coalesce(p1score,0) as p1score, 
coalesce(nscorers,0) as nscorers, r.onwiki as onwiki, r2.offwiki as offwiki, 
r3.future as future, r6.englishAbility as englishAbility, 0.5*r.onwiki + 
0.15*r2.offwiki + 0.25*r3.future + 0.1*r6.englishAbility as p2score from 
scholarships s
-                       left outer join (select scholarship_id, avg(rank) as 
onwiki from rankings where criterion = 'onwiki' group by scholarship_id) r on 
s.id = r.scholarship_id
-                       left outer join (select scholarship_id, avg(rank) as 
offwiki from rankings where criterion = 'offwiki' group by scholarship_id) r2 
on s.id = r2.scholarship_id
-                       left outer join (select scholarship_id, avg(rank) as 
future from rankings where criterion = 'future' group by scholarship_id) r3 on 
s.id = r3.scholarship_id
-                       left outer join (select scholarship_id, avg(rank) as 
englishAbility from rankings where criterion = 'englishAbility' group by 
scholarship_id) r6 on s.id = r6.scholarship_id
-                       left outer join (select scholarship_id, sum(rank) as 
p1score from rankings where criterion = 'valid' group by scholarship_id) r4 on 
s.id = r4.scholarship_id
-                       left outer join (select scholarship_id, count(distinct 
user_id) as nscorers from rankings where criterion in ('onwiki','offwiki', 
'future', 'englishAbility') group by scholarship_id) r5 on s.id = 
r5.scholarship_id
-                       left outer join iso_countries c on s.residence = c.code 
";
 
+       public function getP2List( $partial, $region ) {
                $params = array();
 
-               if ( $region != 'All' ) {
-                       $params[] = $region;
-                       $sql .= 'inner join iso_countries c1 on c.region = ? ';
-               }
-
-               if ( $partial == 2 ) {
-                       $sql .= "group by s.id, s.fname, s.lname, s.email, 
s.residence
-                               having p1score >= 3 and s.exclude = 0 order by 
p2score desc";
-
-               } else {
-                       $params[] = $partial;
-                       $sql .= 'group by s.id, s.fname, s.lname, s.email, 
s.residence
-                               having p1score >= 3 and s.exclude = 0 and 
partial = ? order by p2score desc';
-               }
-
-               return $this->fetchAll( $sql, $params );
-       }
-
-       // Final scoring
-
-       public function getFinalScoring( $partial ) {
                $fields = array(
                        "s.id",
                        "s.fname",
@@ -572,102 +491,85 @@
                        "s.residence",
                        "s.exclude",
                        "s.sex",
-                       "YEAR(NOW())-YEAR(s.dob) AS age",
+                       "YEAR(NOW()) - YEAR(s.dob) AS age",
                        "(s.canpaydiff * s.wantspartial) AS partial",
                        "c.country_name",
-                       "coalesce(p1score, 0) AS p1score",
-                       "coalesce(nscorers, 0) AS nscorers",
-                       "r.onwiki AS onwiki",
-                       "r2.offwiki AS offwiki",
-                       "r3.future AS future",
-                       "r6.englishAbility AS englishAbility",
-                       "0.5*r.onwiki + 0.15*r2.offwiki + 0.25*r3.future + 
0.1*r6.englishAbility as p2score",
+                       "COALESCE(p1score, 0) AS p1score",
+                       "COALESCE(nscorers, 0) AS nscorers",
+                       "ow.onwiki AS onwiki",
+                       "ofw.offwiki AS offwiki",
+                       "f.future AS future",
+                       "ea.englishAbility AS englishAbility",
+                       // FIXME: hardcoded scoring formula
+                       "((0.5 * ow.onwiki) + (0.15 * ofw.offwiki) + (0.25 * 
f.future) + (0.1 * ea.englishAbility)) as p2score",
                );
 
-               $sqlRankOnwiki = self::concat(
-                       "SELECT scholarship_id, AVG(rank) AS onwiki",
-                       "FROM rankings",
-                       "WHERE criterion = 'onwiki'",
-                       "GROUP BY scholarship_id"
-               );
+               $sqlOnWiki = $this->makeAggregateRankSql( 'onwiki', 'AVG' );
+               $sqlOffWiki = $this->makeAggregateRankSql( 'offwiki', 'AVG' );
+               $sqlFuture = $this->makeAggregateRankSql( 'future', 'AVG' );
+               $sqlEnglish = $this->makeAggregateRankSql( 'englishAbility', 
'AVG' );
+               $sqlP1Score = $this->makeAggregateRankSql( 'valid', 'SUM', 
'p1score' );
 
-               $sqlRankOffwiki = self::concat(
-                       "SELECT scholarship_id, AVG(rank) AS offwiki",
-                       "FROM rankings",
-                       "WHERE criterion = 'offwiki'",
-                       "GROUP BY scholarship_id"
-               );
-
-               $sqlRankFuture = self::concat(
-                       "SELECT scholarship_id, AVG(rank) AS future",
-                       "FROM rankings",
-                       "WHERE criterion = 'future'",
-                       "GROUP BY scholarship_id"
-               );
-
-               $sqlRankEnglish = self::concat(
-                       "SELECT scholarship_id, AVG(rank) AS englishAbility",
-                       "FROM rankings",
-                       "WHERE criterion = 'englishAbility'",
-                       "GROUP BY scholarship_id"
-               );
-
-               $sqlRankP1 = self::concat(
-                       "SELECT scholarship_id, AVG(rank) AS p1score",
-                       "FROM rankings",
-                       "WHERE criterion = 'valid'",
-                       "GROUP BY scholarship_id"
-               );
-
-               $sqlNScores = self::concat(
+               $sqlNumScorers = self::concat(
                        "SELECT scholarship_id, COUNT(DISTINCT user_id) AS 
nscorers",
                        "FROM rankings",
-                       "WHERE criterion IN 
('onwiki','offwiki','future','englishAbility')",
+                       "WHERE criterion <> 'valid'",
                        "GROUP BY scholarship_id"
                );
 
+               if ( $region != 'All' ) {
+                       $params['region'] = $region;
+                       $regionJoin = 'INNER JOIN iso_countries c1 ON c.region 
= :region';
+               } else {
+                       $regionJoin = '';
+               }
+
+               if ( $partial == 2 ) {
+                       $havingPartial = '';
+
+               } else {
+                       $params['int_partial'] = $partial;
+                       $havingPartial = 'AND partial = :int_partial';
+               }
+
                $sql = self::concat(
-                       "SELECT " . implode( ',', $fields),
+                       "SELECT", implode( ',', $fields ),
                        "FROM scholarships s",
-                       "LEFT OUTER JOIN ( {$sqlRankOnwiki} ) r ON s.id = 
r.scholarship_id",
-                       "LEFT OUTER JOIN ( {$sqlRankOffwiki} ) r2 on s.id = 
r2.scholarship_id",
-                       "LEFT OUTER JOIN ( {$sqlRankFuture} ) r3 on s.id = 
r3.scholarship_id",
-                       "LEFT OUTER JOIN ( {$sqlRankEnglish} ) r6 on s.id = 
r6.scholarship_id",
-                       "LEFT OUTER JOIN ( {$sqlRankP1} ) r4 on s.id = 
r4.scholarship_id",
-                       "LEFT OUTER JOIN ( {$sqlNScores} ) r5 on s.id = 
r5.scholarship_id",
+                       "LEFT OUTER JOIN ({$sqlOnWiki}) ow ON s.id = 
ow.scholarship_id",
+                       "LEFT OUTER JOIN ({$sqlOffWiki}) ofw ON s.id = 
ofw.scholarship_id",
+                       "LEFT OUTER JOIN ({$sqlFuture}) f ON s.id = 
f.scholarship_id",
+                       "LEFT OUTER JOIN ({$sqlEnglish}) ea ON s.id = 
ea.scholarship_id",
+                       "LEFT OUTER JOIN ({$sqlP1Score}) p1 ON s.id = 
p1.scholarship_id",
+                       "LEFT OUTER JOIN ({$sqlNumScorers}) ns ON s.id = 
ns.scholarship_id",
                        "LEFT OUTER JOIN iso_countries c ON s.residence = 
c.code",
-                       "GROUP BY s.id, s.fname, s.lname, s.email, s.residence",
-                       "HAVING p1score >= 3 AND s.exclude = 0 AND partial = ?",
-                       "ORDER BY p2score desc"
+                       $regionJoin,
+                       'GROUP BY s.id, s.fname, s.lname, s.email, s.residence',
+                       //FIXME: hardcoded score
+                       'HAVING p1score >= 3 AND s.exclude = 0',
+                       $havingPartial,
+                       'ORDER BY p2score DESC'
                );
 
-               return $this->fetchAll( $sql, array( $partial ) );
+               return $this->fetchAll( $sql, $params );
        }
+
+
+       // Final scoring
+       public function getFinalScoring( $partial ) {
+               return $this->getP2List( $partial, 'All' );
+       }
+
 
        // Country administration
-
        public function getListOfCountries( $order = "country_name" ) {
-               $fields = array(
-                       "c.code",
-                       "c.country_name",
-                       "c.region",
-                       "s.sid",
-               );
-               $sqlCountByResidence = self::concat(
-                       "SELECT COUNT(id) AS sid, residence AS attendees",
-                       "FROM scholarships",
-                       "WHERE rank = 1",
-                       "AND exclude = 0",
-                       "GROUP BY residence"
-               );
-               $sql = self::concat(
-                       "SELECT " . implode( ',', $fields ),
-                       "FROM iso_countries c",
-                       "LEFT JOIN ( {$sqlCountByResidence} ) s ON c.code = 
s.attendees",
-                       "ORDER BY ?"
-               );
-               return $this->fetchAll( $sql, array( $order ) );
+               return $this->fetchAll( self::concat(
+                       "SELECT count(*) as sid, c.country_name, c.region",
+                       "FROM scholarships s",
+                       "LEFT JOIN iso_countries c ON c.code = s.residence",
+                       "GROUP BY c.country_name"
+               ) );
        }
+
 
        public function getListOfRegions() {
                return $this->fetchAll( self::concat(
@@ -678,10 +580,28 @@
                ) );
        }
 
-       public function GetPhase1EarlyRejectsTemp() {
-               $res = $this->fetchAll( "select s.id, s.fname, s.lname, 
s.email, s.exclude, coalesce(p1score,0) as p1score from scholarships s
-                       left outer join (select scholarship_id, sum(rank) as 
p1score from rankings where criterion = 'valid' group by scholarship_id) r2 on 
s.id = r2.scholarship_id
-                       group by s.id, s.fname, s.lname, s.email
-                       having p1score < 3 and s.exclude = 0 and s.id>305" );
+
+       /**
+        * Create an SQL query that will compute an aggregate value for all 
rankings
+        * of a given criterion.
+        *
+        * @param string $criterion Ranking criterion to aggregate
+        * @param string $func Aggregate function (AVG,SUM,MAX,MIN,...)
+        * @param string $alias Aggregate column alias
+        * @return string SQL to compute the desired aggregate for all 
scholarships
+        */
+       protected function makeAggregateRankSql(
+               $criterion, $func, $alias = null ) {
+               $alias = $alias ?: $criterion;
+               // $criterion isn't user input but we'll be paranoid anyway
+               $criterion = $this->dbh->quote( $criterion );
+
+               return self::concat(
+                       "SELECT scholarship_id, {$func}(rank) AS {$alias}",
+                       "FROM rankings",
+                       "WHERE criterion = {$criterion}",
+                       "GROUP BY scholarship_id"
+               );
        }
+
 }
diff --git a/src/Wikimania/Scholarship/Dao/User.php 
b/src/Wikimania/Scholarship/Dao/User.php
index d349140..44e45f4 100644
--- a/src/Wikimania/Scholarship/Dao/User.php
+++ b/src/Wikimania/Scholarship/Dao/User.php
@@ -70,8 +70,9 @@
        }
 
        public function newUserCreate( $answers ) {
-               // FIXME: yuck, order matters in $answers!
-               $fields = array( 'username', 'password', 'email', 'reviewer', 
'isvalid', 'isadmin' );
+               $fields = array(
+                       'username', 'password', 'email', 'reviewer', 'isvalid', 
'isadmin'
+               );
                $placeholders = array();
                $vals = array();
                foreach ( $fields as $field ) {
@@ -83,31 +84,46 @@
                        implode( ', ', $fields ) . ') VALUES (' .
                        implode( ',', $placeholders ) . ')';
 
-               // FIXME: prior implementation died on error, fix callers
                return $this->insert( $sql, $vals );
        }
 
+       /**
+        * @param array $answers Updated user data
+        * @param int $id User id
+        * @return bool True if update suceeded, false otherwise
+        */
        public function updateUserInfo( $answers, $id ) {
-               $fields = array( 'username', 'email', 'reviewer', 'isvalid', 
'isadmin', 'blocked' );
+               $fields = array(
+                       'username', 'email', 'reviewer', 'isvalid', 'isadmin', 
'blocked'
+               );
                $placeholders = array();
                foreach ( $fields as $field ) {
                        $placeholders[] = "{$field} = :{$field}";
                }
-               $stmt = $this->dbh->prepare(
-                       'UPDATE users SET ' .
-                       implode( ', ', $placeholders ) .
-                       ' WHERE id = :id'
+
+               $sql = self::concat(
+                       'UPDATE users SET',
+                       implode( ', ', $placeholders ),
+                       'WHERE id = :id'
                );
+               $stmt = $this->dbh->prepare( $sql );
                $answers['id'] = $id;
 
                try {
                        $this->dbh->beginTransaction();
                        $stmt->execute( $answers );
                        $this->dbh->commit();
+                       return true;
+
                } catch ( PDOException $e) {
                        $this->dbh->rollback();
-                       // FIXME
-                       die( "Fatal Error: {$e->getMessage()}" );
+                       $this->logger->error( 'Failed to update user', array(
+                               'method' => __METHOD__,
+                               'exception' => $e,
+                               'sql' => $sql,
+                               'params' => $answers,
+                       ) );
+                       return false;
                }
        }
 
@@ -117,8 +133,13 @@
                                'SELECT password FROM users WHERE id = ?',
                                array( $id )
                        );
+
                        if ( !Password::comparePasswordToHash( $oldpw, 
$res['password'] ) ) {
                                // passsword doesn't match expected
+                               $this->logger->notice( 'Invalid old password; 
will not update', array(
+                                       'method' => __METHOD__,
+                                       'user' => $id,
+                               ) );
                                return false;
                        }
                }
@@ -128,10 +149,18 @@
                        $this->dbh->beginTransaction();
                        $stmt->execute( array( Password::encodePassword( $newpw 
), $id ) );
                        $this->dbh->commit();
+                       $this->logger->notice( 'Changed password for user', 
array(
+                                       'method' => __METHOD__,
+                                       'user' => $id,
+                       ) );
                        return true;
+
                } catch ( PDOException $e) {
                        $this->dbh->rollback();
-                       //FIXME: logging
+                       $this->logger->error( 'Failed to update password for 
user', array(
+                               'method' => __METHOD__,
+                               'exception' => $e,
+                       ) );
                        return false;
                }
        }

-- 
To view, visit https://gerrit.wikimedia.org/r/98024
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I9633cbf5fe7fd1dc9a2c0b31ff20e4209f140be0
Gerrit-PatchSet: 1
Gerrit-Project: wikimedia/wikimania-scholarships
Gerrit-Branch: master
Gerrit-Owner: BryanDavis <[email protected]>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to