Skip to content
Snippets Groups Projects
Select Git revision
  • 31d999ae3c8e6db35b082e4fe2e932b448b8f492
  • master default protected
2 results

TandemMatching.class.php

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    TandemMatching.class.php 10.85 KiB
    <?php
    
    /**
     * This file is part of the TandemPlugin for Stud.IP
     *
     * 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.
     *
     * @author   Moritz Strohm <strohm@data-quest.de>
     * @license  http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
     * @category Plugin
     **/
    
    
    require_once(__DIR__ . '/../models/TandemUserMotherLanguage.class.php');
    
    /**
     * This class implements the matching functionality.
     */
    class TandemMatching
    {
    
        static public function getEqualOrHigherLevels(string $level)
        {
            $all_levels = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'];
            if (!in_array($level, $all_levels)) {
                return [];
            }
            if ($level == 'A1') {
                return $all_levels;
            }
            return array_slice($all_levels, array_search($level, $all_levels));
        }
    
    
        /**
         * Helper method for building the SQL query for findMatches and countMatches.
         * Since both methods use the same query and differ only in the result
         * the query can be built in this helper method.
         *
         * @return Array Associative array with two attributes:
         *     - 'query': The SQL query
         *     - 'params': Parameters for the SQL query
         */
        static protected function buildMatchSqlQuery(TandemProfile $tandem_request, $limit = 0, $offer_user_id = null)
        {
            //if the gender attribute is set we must convert its value to
            //the Stud.IP system values: 1 = male, 2 = female, 0 = unspecified:
    
            $request_gender = null;
    
            if($tandem_request->gender and TandemPlugin::isGenderSearchEnabled()) {
                //If gender search is enabled and a gender preference is set
                //we must filter for those settings.
                if($tandem_request->gender == 'm') {
                    $request_gender = 1;
                } elseif($tandem_request->gender == 'w') {
                    $request_gender = 2;
                }
            }
    
            $sql = 'INNER JOIN tandem_user_mother_languages as tuml
                    ON tuml.user_id = tandem_profiles.user_id ';
            $sql_params = [];
    
            if($request_gender) {
                //If the gender attribute from the request was converted correctly
                //we must also search for the gender information using the user_info table.
                $sql .= 'INNER JOIN user_info
                        ON tuml.user_id = user_info.user_id ';
            }
    
            $sql .= 'WHERE tuml.language_id = :target_language_id
                AND tuml.user_id <> :user_id
                AND tandem_profiles.target_language_id IN ( :request_user_mother_language_ids ) ';
            if (Config::get()->TANDEMPLUGIN_USE_LEVEL) {
                $sql .= "AND tuml.`level` IN ( :other_minimum_levels ) ";
                //Check which level is higher: The requested one or the one of the user.
                $user_and_higher_levels = self::getEqualOrHigherLevels($tandem_request->level);
                if ($tandem_request->requested_level) {
                    $requested_and_higher_levels = self::getEqualOrHigherLevels($tandem_request->requested_level);
                    //Use the level array that has the least entries so that no lower levels as the requested
                    //ones are used.
                    $sql_params['other_minimum_levels'] = count($user_and_higher_levels) < count($requested_and_higher_levels)
                        ? $user_and_higher_levels : $requested_and_higher_levels;
                } else {
                    //Easy: Use the level of the user.
                    $sql_params['other_minimum_levels'] = $user_and_higher_levels;
                }
            }
    
            //We must check, if there are tandem pairs. Profiles in tandem pairs
            //with pair status 1 (accepted) and 0 (requested) are filtered out.
            //Also all profiles where a tandem request was made to are filtered out.
    
            $pairs_exist = (TandemPair::countBySql('TRUE') > 0);
    
            if($pairs_exist) {
                //QUERY EXPLAINATION:
                // tandem_profiles.id NOT IN:
                //     We must filter all profiles that are bound to a pair matching
                //     the following criteria.
                //
                // SELECT request_profile_id FROM tandem_pairs WHERE status > '-1'
                // AND request_profile_id = :profile_id:
                //     The profile-ID of the profile we're searching matches for
                //     must match either the request_profile_id or the
                //     offer_profile_id. The latter filters out all requests
                //     and established pairs that have been made with that profile.
                //
                // SELECT offer_profile_id FROM tandem_pairs WHERE status > '-1':
                //     The same as with request_profile_id but here we're selecting
                //     the offer_profile_ids and use them in a UNION.
                //
                // SELECT request_profile_id FROM tandem_pairs WHERE status = '1'
                // and
                // SELECT offer_profile_id FROM tandem_pairs WHERE status = '1':
                //     We select the request_profile_ids and offer_profile_ids
                //     of all established pairs.
                // )
    
                $sql .= "AND tandem_profiles.id NOT IN (
                    SELECT request_profile_id FROM tandem_pairs WHERE request_profile_id = :profile_id OR offer_profile_id = :profile_id
                    UNION
                    SELECT offer_profile_id FROM tandem_pairs WHERE offer_profile_id = :profile_id OR request_profile_id = :profile_id
                    UNION
                    SELECT request_profile_id FROM tandem_pairs WHERE status = '1'
                    UNION
                    SELECT offer_profile_id FROM tandem_pairs WHERE status = '1'
                ) ";
            }
    
            $request_user_mother_language_ids = [];
            $rumls = TandemUserMotherLanguage::findBySql(
                'user_id = :user_id',
                ['user_id' => $tandem_request->user_id]
            );
    
            foreach($rumls as $ruml) {
                $request_user_mother_language_ids[] = $ruml->language_id;
            }
    
            $sql_params['target_language_id'] = $tandem_request->target_language_id;
            $sql_params['user_id'] = $tandem_request->user_id;
            $sql_params['request_user_mother_language_ids'] = $request_user_mother_language_ids;
    
            if($request_gender) {
                //The offerer must have the gender the requester wishes.
                $sql .= 'AND user_info.geschlecht = :request_gender ';
                $sql_params['request_gender'] = $request_gender;
            }
    
            if(TandemPlugin::isGenderSearchEnabled()) {
                //The requester must have the gender the offerer wishes.
                $requester_gender = $tandem_request->user->geschlecht;
    
                //convert Stud.IP gender to TandemPlugin gender:
                if($requester_gender == '1') {
                    $requester_gender = 'm';
                } elseif($requester_gender == '2') {
                    $requester_gender = 'w';
                } else {
                    $requester_gender = '';
                }
    
                if($requester_gender != '') {
                    //In cases where the gender is relevant
                    //we must extend the SQL query and check if the offerer
                    //wants requests from users with the requester's gender:
                    $sql .= "AND tandem_profiles.gender IN ( '', :requester_gender ) ";
                    $sql_params['requester_gender'] = $requester_gender;
                } else {
                    //If the requester has no gender stored in the user data
                    //the matching profiles must not have a gender preference.
                    $sql .= "AND tandem_profiles.gender = '' ";
                }
            }
    
            if($offer_user_id != null) {
                $sql .= 'AND tandem_profiles.user_id = :offer_user_id ';
                $sql_params['offer_user_id'] = $offer_user_id;
            }
    
            if($pairs_exist) {
                $sql_params['profile_id'] = $tandem_request->id;
            }
    
            $sql .= ' GROUP BY tandem_profiles.id, tandem_profiles.user_id ';
    
            if($limit > 0) {
                $sql .= 'LIMIT :limit';
                $sql_params['limit'] = $limit;
            }
    
            return [
                'query' => $sql,
                'params' => $sql_params
            ];
        }
    
    
        /**
         * Finds matches for a given tandem request.
         *
         * @param TandemProfile $tandem_request A tandem profile
         *     that shall be checked for matches.
         * @param int $limit Limits the number of matches.
         * @param string $offer_user_id Limits the search for profiles for one offerer.
         *
         * @return TandemProfile[] List of tandem profiles that match the criteria
         *     specified in $tandem_request.
         */
        static public function findMatches(TandemProfile $tandem_request, $limit = 0, $offer_user_id = null)
        {
            //Check, if the profile is already linked to an established tandem pair:
            $pair_count = TandemPair::countBySql(
                "(request_profile_id = :profile_id
                OR offer_profile_id = :profile_id)
                AND status = '1'",
                [
                    'profile_id' => $tandem_request->id
                ]
            );
    
            if($pair_count > 0) {
                //We don't look for matches if there is already a pair!
                return [];
            }
    
            $query = self::buildMatchSqlQuery($tandem_request, $limit, $offer_user_id);
    
            return TandemProfile::findBySql($query['query'], $query['params']);
        }
    
    
        /**
         * Counts matches for a given tandem profile.
         *
         * @param TandemProfile $tandem_request A tandem profile
         *     that shall be checked for matches.
         * @param string $offer_user_id Limits the search for profiles for one offerer.
         *
         * @return int Amount of matches for the profile.
         */
        static public function countMatches(TandemProfile $tandem_request, $offer_user_id = null)
        {
            //Check, if the profile is already linked to an established tandem pair:
            $pair_count = TandemPair::countBySql(
                "(request_profile_id = :profile_id
                OR offer_profile_id = :profile_id)
                AND status > '-1'",
                [
                    'profile_id' => $tandem_request->id
                ]
            );
    
            if($pair_count > 0) {
                //We don't count matches if there is already a pair!
                return 0;
            }
    
            $query = self::buildMatchSqlQuery($tandem_request, 0, $offer_user_id);
    
            return TandemProfile::countBySql($query['query'], $query['params']);
        }
    
    
    
        static public function matchesExist(Array $profiles = [])
        {
            if(!$profiles) {
                //Empty array means we can't look for matches!
                return false;
            }
    
    
            foreach($profiles as $profile) {
                $count_result = self::countMatches($profile);
    
                if($count_result > 0) {
                    //We have found one match: That's enough to see if a match exists:
                    return true;
                }
            }
    
            //No matches found:
            return false;
        }
    }