make the cutoff donation amount a config variable
[fsfdrupalauth.git] / lib / Auth / Source / FSFDrupalAuth.php
index 669b1ccc9e95058e15db99ae56b4f6346d66389a..69f60b473f2b26e4d9550c1bb477822c958bd9f7 100644 (file)
@@ -45,6 +45,35 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
     private $query_membership;
     private $query_staff;
 
+    private $query_nomination_process_donations;
+    private $query_nomination_process_gift_receipt;
+    private $query_nomination_process_adhoc;
+
+    private $query_discussion_process_old_membership;
+    private $query_discussion_process_donations;
+    private $query_discussion_process_adhoc;
+
+    /**
+     * SQL query parameters, or variables that help determine which attributes
+     * someone has
+     */
+    private $fsf_org_id;
+    private $gift_redeem_page_id;
+
+    private $nomination_process_active;
+    private $nomination_process_contrib_start_date;
+    private $nomination_process_contrib_end_date;
+    private $nomination_process_adhoc_access_group_id;
+    private $membership_monthly_rate;
+    private $student_membership_monthly_rate;
+
+    private $discussion_process_active;
+    private $discussion_process_contrib_start_date;
+    private $discussion_process_contrib_end_date;
+    private $discussion_process_adhoc_access_group_id;
+    private $discussion_process_adhoc_no_access_group_id;
+    private $discussion_process_donation_amount;
+
     /**
      * Constructor for this authentication source.
      *
@@ -60,7 +89,40 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
         parent::__construct($info, $config);
 
         // Make sure that all required parameters are present.
-        foreach (['dsn', 'username', 'password', 'query_main', 'query_membership', 'query_staff'] as $param) {
+       foreach (['dsn',
+               'username',
+               'password',
+
+               'query_main',
+               'query_membership',
+               'query_staff',
+
+               'query_nomination_process_donations',
+               'query_nomination_process_gift_receipt',
+               'query_nomination_process_adhoc',
+
+               'fsf_org_id',
+               'gift_redeem_page_id',
+
+               'nomination_process_active',
+               'nomination_process_contrib_start_date',
+               'nomination_process_contrib_end_date',
+               'nomination_process_adhoc_access_group_id',
+               'membership_monthly_rate',
+               'student_membership_monthly_rate',
+
+               'query_discussion_process_old_membership',
+               'query_discussion_process_donations',
+               'query_discussion_process_adhoc',
+
+               'discussion_process_active',
+               'discussion_process_contrib_start_date',
+               'discussion_process_contrib_end_date',
+               'discussion_process_adhoc_access_group_id',
+               'discussion_process_adhoc_no_access_group_id',
+               'discussion_process_donation_amount',]
+               as $param) {
+
             if (!array_key_exists($param, $config)) {
                 throw new Exception('Missing required attribute \''.$param.
                     '\' for authentication source '.$this->authId);
@@ -72,14 +134,10 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
                     ' to be a string. Instead it was: '.
                     var_export($config[$param], true));
             }
+
+            $this->$param = $config[$param];
         }
 
-        $this->dsn = $config['dsn'];
-        $this->username = $config['username'];
-        $this->password = $config['password'];
-        $this->query_main =       $config['query_main'];
-        $this->query_membership = $config['query_membership'];
-        $this->query_staff =      $config['query_staff'];
         if (isset($config['options'])) {
             $this->options = $config['options'];
         }
@@ -89,7 +147,7 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
     /**
      * Create a database connection.
      *
-     * @return \PDO  The database connection.
+     * @return PDO  The database connection.
      */
     private function connect()
     {
@@ -137,9 +195,9 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
         // pipes code based off of https://www.php.net/manual/en/function.proc-open.php
         // CC-BY 3.0 or later
         $descriptorspec = array(
-           0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
-           1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
-           2 => array("pipe", "w") // stderr is a file to write to
+           0 => array("pipe", "r"),  // stdin is a pipe that the child may read from
+           1 => array("pipe", "w"),  // stdout is a pipe that the child may write to
+           2 => array("pipe", "w")   // stderr is a pipe that the child may write to
         );
 
         $cwd = "../modules/fsfdrupalauth/extlib";
@@ -167,17 +225,19 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
             $return_value = proc_close($process);
 
             //Logger::debug('fsfdrupalauth:'.$this->authId.': authenticator stdout: '.$result);
-    
+
+            $errors_found_yet = false;
             if ($errors != "") {
                 Logger::error('fsfdrupalauth:'.$this->authId.': authenticator stderr: '.$errors);
+                $errors_found_yet = true;
             }
-    
+
             if ($return_value != 0) {
                 Logger::error('fsfdrupalauth:'.$this->authId.': authenticator non-zero return code: '.$return_value);
-                return false;
+                $errors_found_yet = true;
             }
-    
-            return ($return_value == 0 && rtrim($result) == "true");
+
+            return (!$errors_found_yet && is_string($result) && rtrim($result) == "true");
 
         } else {
 
@@ -192,7 +252,7 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
      * query the database with arbitrary queries that only require a user name.
      *
      */
-    private function query_db($queryname, $username)
+    private function query_db($queryname, $query_params)
     {
         assert(is_string($queryname));
         assert(is_string($username));
@@ -207,14 +267,14 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
         }
 
         try {
-            $sth->execute(['username' => $username]);
+            $sth->execute($query_params);
         } catch (PDOException $e) {
             throw new Exception('fsfdrupalauth:'.$this->authId.
                 ': - Failed to execute queryname: '.$queryname.': '.$e->getMessage());
         }
 
         try {
-            $data = $sth->fetchAll(\PDO::FETCH_ASSOC);
+            $data = $sth->fetchAll(PDO::FETCH_ASSOC);
         } catch (PDOException $e) {
             throw new Exception('fsfdrupalauth:'.$this->authId.
                 ': - Failed to fetch result set: '.$e->getMessage());
@@ -231,11 +291,11 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
      */
     private function add_more_attributes(&$attributes, $username) {
 
-       //
-       // query on membership
-       //
+        //
+        // query on membership
+        //
 
-        $membership_data = $this->query_db('query_membership', $username);
+        $membership_data = $this->query_db('query_membership', ['username' => $username]);
 
         if (count($membership_data) === 0) {
             // No rows returned - invalid username
@@ -243,7 +303,7 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
                 ': No rows in result set. Probably no membership.');
         }
 
-       $attributes['is_member'] = ['false'];
+        $attributes['is_member'] = ['false'];
         $attributes['was_member'] = ['false'];
 
         foreach ($membership_data as $row) {
@@ -253,20 +313,201 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
                 }
                 $value = (string) $value;
 
-               if ($value === '1' || $value === '2' || $value === '3') {
+                if ($value === '1' || $value === '2' || $value === '3') {
                     $attributes['is_member'] = ['true'];
                     $attributes['was_member'] = ['true'];
-               } elseif ($value === '4') {
+                } elseif ($value === '4') {
                     $attributes['was_member'] = ['true'];
-               }
+                }
             }
         }
 
+        //
+        // helper functions for access to board nomination / discussion process
+        //
+
+       /**
+         * @param string $query_name  Name of query in authsources
+         * @param array $extra_params  Associative array of parameters to include in query
+        */
+       $donation_query = function ($query_name, $extra_params)
+           use ($username) {
+
+               $parameters = ['username' => $username];
+
+               foreach ($extra_params as $key => $value) {
+                       $parameters[$key] = $value;
+               }
+
+               return $this->query_db($query_name, $parameters);
+       };
+
+       $old_membership_query = $donation_query;
+
+       $compare_res = function ($result, $amount) {
+               foreach ($result[0] as $key => $value) {
+                       if (intval($value) >= $amount) {
+                               return true;
+                       }
+               }
+               return false;
+       };
+
+       // set dates here, used by helper functions below
+       $nomination_process_start_date = $this->nomination_process_contrib_start_date;
+       $nomination_process_end_date   = $this->nomination_process_contrib_end_date;
+       $discussion_process_start_date = $this->discussion_process_contrib_start_date;
+       $discussion_process_end_date   = $this->discussion_process_contrib_end_date;
+
+
+       // looks for memberships / comparable donations in time window. also
+       // looks for a membership or donation (included as a param) that
+       // occurred up to a year before, and that would have carried over into
+       // the time window with a single donation. this approximates whether
+       // the person was, or would have been, a member during the configured
+       // time window.
+       $nomination_process_analyze_history = function ($selective_donations_history)
+           use ($nomination_process_start_date, $nomination_process_end_date) {
+
+               $eligible = false;
+
+               $start_date_obj = new \DateTime($nomination_process_start_date);
+               $end_date_obj = new \DateTime($nomination_process_end_date);
+
+               foreach ($selective_donations_history as $row) {
+
+                       $amount = intval($row['amount']);
+                       $member_type_id = $row['member_type_id'];
+                       $receive_date_obj = new \DateTime($row['receive_date']);
+
+                       if ($amount < 5) {
+                               continue;
+
+                       } elseif ($receive_date_obj >= $start_date_obj and $receive_date_obj <= $end_date_obj) {
+                               return true;
+
+                       } elseif ($receive_date_obj < $start_date_obj) {
+                               switch ($member_type_id) {
+                                       case '1':
+                                       case '2':
+                                               $rate = intval($this->student_membership_monthly_rate);
+                                               break;
+                                       case '8':
+                                       case '9':
+                                       case null:
+                                       default:
+                                               $rate = intval($this->membership_monthly_rate);
+                                               break;
+                               }
+                               $membership_end_date_obj = new \DateTime($row['receive_date']);
+                               $membership_end_date_obj->add(new \DateInterval("P" . ceil($amount / $rate) . "M"));
+
+                               if ($membership_end_date_obj >= $start_date_obj) {
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       };
+
+       $discussion_process_analyze_history = function ($selective_donations_history)
+           use ($discussion_process_start_date, $discussion_process_end_date) {
+
+               $eligible = false;
+               $total = 0;
+
+               $start_date_obj = new \DateTime($discussion_process_start_date);
+               $end_date_obj = new \DateTime($discussion_process_end_date);
+
+               foreach ($selective_donations_history as $row) {
+
+                       $amount = intval($row['amount']);
+                       $member_type_id = $row['member_type_id'];
+                       $receive_date_obj = new \DateTime($row['receive_date']);
+
+                       if (($receive_date_obj > $start_date_obj) && ($receive_date_obj < $end_date_obj)) {
+                               $total += $amount;
+                       }
+               }
+
+               if ($total >= $this->discussion_process_donation_amount) {
+                       return true;
+               } else {
+                       return false;
+               }
+       };
+
        //
-       // query on staff
+       // nomination form participation specific checks
        //
 
-        $staff_data = $this->query_db('query_staff', $username);
+       $donation_params    = ['start_date' => $nomination_process_start_date, 'end_date' => $nomination_process_end_date];
+       $gift_member_params = ['start_date' => $nomination_process_start_date, 'end_date' => $nomination_process_end_date, 'gift_redeem_page_id' => intval($this->gift_redeem_page_id)];
+       $adhoc_params       = ['adhoc_access_group_id' => intval($this->nomination_process_adhoc_access_group_id)];
+
+       if ($this->nomination_process_active != 'true' ) {
+               $attributes['nomination_process'] = ['false'];
+
+       } elseif ($compare_res($donation_query('query_nomination_process_adhoc', $adhoc_params), 1)) {
+               $attributes['nomination_process'] = ['true'];
+
+       } elseif ($attributes['is_member'] != ['true']) {
+               Logger::debug('fsfdrupalauth:'.$this->authId.
+                       ': Not a member / comparable donor during window for board process.');
+               $attributes['nomination_process'] = ['false'];
+
+       } elseif ($nomination_process_analyze_history($donation_query('query_nomination_process_donations', $donation_params))
+               || $compare_res($donation_query('query_nomination_process_gift_receipt', $gift_member_params), 1)) {
+
+               $attributes['nomination_process'] = ['true'];
+
+       } else {
+               Logger::debug('fsfdrupalauth:'.$this->authId.
+                       ': Not a member / comparable donor during window for board process.');
+               $attributes['nomination_process'] = ['false'];
+       }
+
+       //
+       // discussion form participation specific checks
+       //
+
+       $donation_params    = ['start_date' => $discussion_process_start_date, 'end_date' => $discussion_process_end_date];
+       $old_member_params  = $donation_params;
+       $adhoc_params       = ['adhoc_access_group_id' => intval($this->discussion_process_adhoc_access_group_id)];
+       $adhoc_params_no    = ['adhoc_access_group_id' => intval($this->discussion_process_adhoc_no_access_group_id)];
+
+       if ($this->discussion_process_active != 'true' ) {
+               $attributes['discussion_process'] = ['false'];
+
+       } elseif ($compare_res($donation_query('query_discussion_process_adhoc', $adhoc_params_no), 1)) {
+               Logger::debug('fsfdrupalauth:'.$this->authId.
+                       ': Nominee not eligible for board nominee discussion process.');
+               $attributes['discussion_process'] = ['false'];
+
+       } elseif ($compare_res($donation_query('query_discussion_process_adhoc', $adhoc_params), 1)) {
+               $attributes['discussion_process'] = ['true'];
+
+       } elseif ($attributes['is_member'] != ['true']) {
+               Logger::debug('fsfdrupalauth:'.$this->authId.
+                       ': Not eligible for board nominee discussion process.');
+               $attributes['discussion_process'] = ['false'];
+
+       } elseif ($compare_res($old_membership_query('query_discussion_process_old_membership', $old_member_params), 1)
+               || $discussion_process_analyze_history($donation_query('query_discussion_process_donations', $donation_params))) {
+
+               $attributes['discussion_process'] = ['true'];
+
+       } else {
+               Logger::debug('fsfdrupalauth:'.$this->authId.
+                       ': Not eligible for board nominee discussion process.');
+               $attributes['discussion_process'] = ['false'];
+       }
+
+        //
+        // query on staff
+        //
+
+        $staff_data = $this->query_db('query_staff', ['username' => $username, 'fsf_org_id' => $this->fsf_org_id]);
 
         if (count($staff_data) === 0) {
             // No rows returned - invalid username
@@ -274,7 +515,7 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
                 ': No rows in result set. Probably not FSF staff.');
         }
 
-       $attributes['is_fsf_staff'] = ['false'];
+        $attributes['is_fsf_staff'] = ['false'];
 
         foreach ($staff_data as $row) {
             foreach ($row as $key => $value) {
@@ -284,13 +525,31 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
                 }
                 $value = (string) $value;
 
-                if ($value === $username) {
+                if (strtolower($value) === strtolower($username)) {
                     // they are staff
-                    $attributes[$key] = ['true'];
+                    $attributes['is_fsf_staff'] = ['true'];
                     break;
                 }
             }
         }
+
+        //
+        // aggregate attribute
+        //
+
+        $groups_list = '';
+        $first = true;
+        foreach ($attributes as $key => $value) {
+            if ($value == ['true']) {
+                if (!$first) {
+                    $groups_list .= ', ';
+                }
+                $groups_list .= $key;
+                $first = false;
+            }
+        }
+
+        $attributes['groups_list'] = [$groups_list];
     }
 
     /**
@@ -315,7 +574,7 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
         //Logger::debug('fsfdrupalauth:'.$this->authId.': entered password: '.$password);
 
 
-        $user_data = $this->query_db('query_main', $username);
+        $user_data = $this->query_db('query_main', ['username' => $username]);
 
 
         if (count($user_data) === 0) {
@@ -331,11 +590,11 @@ class FSFDrupalAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
          */
         $attributes = [];
 
-       // use the entered user name so we don't forcibly change it to all
-       // lower case. this is to preserve the behavior of the old cas server,
-       // and to remain compatible with our MW and Discourse sites that are
-       // case sensitive.
-       $attributes['name'][] = $username;
+        // use the entered user name so we don't forcibly change it to all
+        // lower case. this is to preserve the behavior of the old cas server,
+        // and to remain compatible with our MW and Discourse sites that are
+        // case sensitive.
+        $attributes['name'][] = $username;
 
         foreach ($user_data as $row) {
             foreach ($row as $key => $value) {