Topic-icon URGENT: onLogin Issue after upgrade to latest SCLogin & JFBConnect

Active Subscriptions:

None
Hi Alex, I've been eperiencing some issues with FB login using the SCLogin module since I've updated JFBC and SCLogin to the latest versions.

Basically, in my custom social profile plugin for JFBC/Akeeba Subs, I am using the onLogin() method to retrieve the custom user state var that the socialprofile class creates in the fetchProfileFromFieldMap() method. In my case this is 'plg_socialprofile.facebook.akeebasubs'. I then create my own custom state var from it to make the data ready to inject into any Akeeba Subs forms. Then, in my template overrides for akeebasubs, I detect the presence of my custom state var and if it's not empty, I foreach over it, getting the data to populate the subscription level form and hide the fields accordingly.

However, since upgrading, this no longer works. The reason being that, after logging in with facebook, seeing the 'logging in...' modal window and redirecting back to the previous page, the user is not logged in and the akeeba subs level form is empty.

To debug this, I've echo'd and print_r''d the data before adding a die() statement. From this debugging, I can confirm that the initial 'plg_socialprofile.facebook.akeebasubs' state var does contain the data that I would expect. Also, my method to transform this into akeeba subs ready data returns exactly what I'd expect. Despite this, a successful login is never performed.

Here's my onLogin() method in the plugin class:
protected function onLogin()//scProfilesOnLogin()
{
    // Call parent
    parent::onLogin();
    
    //Get app
    $app = JFactory::getApplication();
    
    //Get this session key containing profile data
    $sessionKey = 'plg_socialprofiles.' . $this->network . '.' . $this->getName();
    $sessionProfile = json_decode($app->getUserState($sessionKey, null));
    
    // Check if the session exists
    if(is_null($sessionProfile))// Fail
        return false;

	$mappedProfile = MyboJFBConnect::mapJFBCProfileToAkeebasubs($sessionProfile);		
    
    // add to custom state var 
    $app->setUserState('mybo.elite.mapped.fb.profile', $mappedProfile);
	MyboSystem::consoleLogSession('mybo.elite.mapped.fb.profile', array('mybo.elite.mapped.fb.profile' => $mappedProfile));

    //return
    return true;
}

It seems to me that something must have changed with this in the latest release - something concerning the onLogin call that is made by socialprofile plugins. Any ideas???

In case it's of any help, here is the complete plugin class:
<?php
/**
 * @package JFBConnect - Akeeba Subscriptions Profile Integration
 * @copyright (C) 2013 by MindYourBizOnline - All rights reserved
 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */
defined('_JEXEC') or die('Restricted access');

jimport('joomla.utilities.date');
jimport('sourcecoast.plugins.socialprofile');
jimport('mybo.jfbconnect');

class plgSocialProfilesAkeebasubs extends SocialProfilePlugin
{
    /**
     * @var bool    $akeebaHelperLoaded
     * Flag to check if Akeebasubs Component Helper has been loaded
     */
    public $akeebaHelperLoaded = false;
    
    function __construct(&$subject, $params)
    {
        $this->_componentFolder = JPATH_SITE . '/components/com_akeebasubs';
        $this->includeAkeebasubsComponentHelper();
        parent::__construct($subject, $params);
        $this->defaultSettings->set('import_avatar', '0');
        $this->defaultSettings->set('import_always', '1');
		
		$this->defaultSettings->set('registration_show_fields', '0'); //0=None, 1=Required, 2=All
        $this->defaultSettings->set('imported_show_fields', '0'); //0=No, 1=Yes
		
    }
    

protected function onLogin()//scProfilesOnLogin()
{
    // Call parent
    parent::onLogin();
    
    //Get app
    $app = JFactory::getApplication();
    
    //Get this session key containing profile data
    $sessionKey = 'plg_socialprofiles.' . $this->network . '.' . $this->getName();
    $sessionProfile = json_decode($app->getUserState($sessionKey, null));
    
    // Check if the session exists
    if(is_null($sessionProfile))// Fail
        return false;

	$mappedProfile = MyboJFBConnect::mapJFBCProfileToAkeebasubs($sessionProfile);		
    
    // add to custom state var 
    $app->setUserState('mybo.elite.mapped.fb.profile', $mappedProfile);
	MyboSystem::consoleLogSession('mybo.elite.mapped.fb.profile', array('mybo.elite.mapped.fb.profile' => $mappedProfile));

    //return
    return true;
}
    
    /**
     * Get field names and inputs to request additional information from users on registration
     * @return string HTML of form fields to display to user on registration
     */
    public function socialProfilesOnShowRegisterForm($network)
    {
        $this->loadSettings($network);
        $profileData = $this->fetchProfileFromFieldMap(false);
        $html = $this->getRegistrationForm($profileData);
        return $html;
    }

	protected function createUser()
    {
        $this->importSocialProfile();
        return true;
    }

    /*protected function createUser($profileData)
    {
        $query = "INSERT IGNORE INTO #__akeebasubs_users (userid) VALUES (" . $this->db->quote($this->joomlaId) . ")";
        $this->db->setQuery($query);
        $this->db->execute();
        
        // Custom fields stored as params
        $akCustomFieldKeys = array("agreetotos", "dob", "phone", "mobile","gender");
        $akCustomFields = '';

        // 
        foreach ($this->getProfileFields() as $name => $displayName){
            // Regular fields
            if(!in_array($name, $akCustomFieldKeys)){
                if($name == "state"){ // We need to pass the country too
                    $this->saveProfileField($name, array($profileData->getFieldWithUserState($name), $profileData->getFieldWithUserState("country")));
                }else{
                   $this->saveProfileField($name, $profileData->getFieldWithUserState($name)); 
                }
                
            }else{ // custom (params) fields
                // concatenate string of values
                $akCustomFields .= '"' . $name . '":"' . $profileData->getFieldWithUserState($name) . '"';
            }
        }
        
        // Check if any custom (params) fields included
        if(is_string($akCustomFields) && strlen($akCustomFields) > 5){
            $akCustomFields = '{' . $akCustomFields . '}';
            // Save the field
            $this->saveProfileField('params', $akCustomFields);
        }
        
    }*/

    protected function saveProfileField($fieldId, $value)
    {
        $addStateToAddress2 = false; // flag - used to determine if state should be appended to address2 field

        // Load the language file for gender used below
        $lang = JFactory::getLanguage();
        $lang->load('com_jfbconnect');

        switch ($fieldId)
        {
            case "dob":
                /*if($this->network == "facebook"){*/
                	$value = new JDate($value);
	                $value = $value->toSql();
				/*}else{
					$value = strtotime($value);
				}*/
                break;
                
            case "gender":
                $value = MyboJFBConnect::getGenderIdFromString($value);
                break;
                
            case "state":
                $originalValue = $value[0]; // The state
                $value = MyboJFBConnect::getLocationIdFromName('states', $value[0], $value[1]); // $value[0] is the country
                if(trim($value[0]) == ""){
                    $addStateToAddress2 = true;
                    $state = $originalValue;
                }
                break;
            
            case "country":
                $value = MyboJFBConnect::getLocationIdFromName('countries', $value);
                break;              
        }
        
        $this->db->setQuery("UPDATE #__akeebasubs_users SET `" . $fieldId . "` = " . $this->db->quote($value) . " WHERE userid=" . $this->joomlaId);
        $this->db->execute();
        
        // If a state was present from FB but doesn't exist in the Akeeba States 
        if($addStateToAddress2){
            // Check that the state isn't already part of address2
            $this->db->setQuery("SELECT address2 FROM #__akeebasubs_users WHERE userid =" . $this->joomlaId);
            $result = $this->db->execute();
            
            // If the state isn't found in address2 field
            if(!strpos($state, $result)){
                //append it to the value
                $this->db->setQuery("UPDATE #__akeebasubs_users SET address2 = CONCAT(address2, ', " . $state . "') WHERE userid=" . $this->joomlaId);
                $this->db->execute();
            }
        } 
    }
    
    /* THIS METHOD ISN'T USED */
    protected function setAvatar($socialAvatar)
    {
        jimport('joomla.filesystem.file');
        jimport('joomla.filesystem.folder');

        $errorDetected = false;
        // Get a hash for the file name.
        $socialAvatarFile = $this->getAvatarPath() . '/' . $socialAvatar;

        //@todo: configurable path for avatar storage?
        $socialExtension = substr($socialAvatar, strpos($socialAvatar, '.'));
        switch ($socialExtension)
        {
            case ".png" :
            case ".gif" :
            case ".jpg" :
                break;
            default:
                $app = JFactory::getApplication();
                $app->enqueueMessage("File type not supported for user " . $this->joomlaId . ", Avatar '" . $socialAvatar . "', type '" . $socialExtension . "'", 'error');
                $errorDetected = true;
        }
        if ($errorDetected)
            return false;

        $storage = JPATH_ROOT . '/media/kunena/avatars/users';
        $avatarImageName = 'avatar' . $this->joomlaId . $socialExtension;

        $storageImage = $storage . '/' . $avatarImageName;

        // Delete any old resized avatars so they'll be regenerated
        $query = "SELECT `avatar` FROM #__kunena_users WHERE userid = " . $this->joomlaId;
        $this->db->setQuery($query);
        $oldAvatar = $this->db->loadResult();
        $deletelist = JFolder::folders(JPATH_ROOT . '/media/kunena/avatars/resized', '.', false, true);
        foreach ($deletelist as $delete)
        {
            if (is_file($delete . '/' . $oldAvatar))
                JFile::delete($delete . '/' . $oldAvatar);
        }

        if (JFile::exists($socialAvatarFile))
            JFile::copy($socialAvatarFile, $storageImage);
        else
            return false;

        $query = "UPDATE #__kunena_users SET `avatar` = " . $this->db->quote('users/' . $avatarImageName) . " WHERE userid = " . $this->joomlaId;
        $this->db->setQuery($query);
        $this->db->execute();

        return true;
    }
    
    
    public function getProfileFields()
    {
		$akeebasubsFields = array();
        $akeebasubsFields[] = (object)array('id' => "name", "name" => "Name");
        $akeebasubsFields[] = (object)array('id' => "gender", "name" => "Gender");
        $akeebasubsFields[] = (object)array('id' => "dob", "name" => "Date of Birth");
		$akeebasubsFields[] = (object)array('id' => "city", "name" => "City");
		$akeebasubsFields[] = (object)array('id' => "state", "name" => "State");
		$akeebasubsFields[] = (object)array('id' => "country", "name" => "Country");

        return $akeebasubsFields;
    }
    
    /**
     * Method to check that the akeeba subs helper is loaded
     */
    protected function includeAkeebasubsComponentHelper()
    {        
        // Check the Akeebasubs Helper exists and include it
        if(file_exists(JPATH_ROOT . DS . "administrator" . DS . "components" . DS . "com_akeebasubs" . DS . "helpers" . DS . "select.php")){
            include_once(JPATH_ROOT . DS . "administrator" . DS . "components" . DS . "com_akeebasubs" . DS . "helpers" . DS . "select.php");
            $this->akeebaHelperLoaded = true;
        }
    }
}
Also, here is my custom library file (Mybo...) that is used to transform the facebook data for akeeba subs:
<?php
/**
 * @package     akeebasubs.admin.assets
 * @copyright   Copyright (c)2013 Geraint Brown - MindYourBizOnline. All rights reserved.
 * @license     GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die('Restricted access');

jimport('joomla.filesystem.file');
jimport('joomla.user.helper');
jimport('mybo.user');
jimport('mybo.timedsubs');
jimport('mybo.system');

/**
 * MyBO JFBConnect Class.  Handles all application interaction with the JFBConnect Akeebasubs Profile Plugin.
 *
 * @package     Joomla.Platform
 * @subpackage  AkeebasubsSocialProfile.Plugin
 * @since       11.1
 */
class MyboJFBConnect
{    
    public static function getJFBCAkeebaProfileSettings()
    {
        //DB
        $db = JFactory::getDbo();
        
        // Load the configuration settings from JFBConnect
        $query = $db->getQuery(true);
        $query->select("value");
        $query->from("#__jfbconnect_config");
        $query->where('setting="profile_akeebasubs"');
        $db->setQuery($query);
        $values = $db->loadResult();
        
        //Debug
        if(empty($values))
            return json_decode(json_encode(null));
        
        //Return
        return json_decode($values);
    }
    
        /**
      * Method to encode/decode facebook profile data from JFBC
      * and map it to Akeeba subscription cache so that the 
      * subscription form is pre-populated
      * 
      * @param  object  $profileData    The object returned from facebook request containing $this->fbFields
      * @return array   $fbDataCheck    Associative array containing Akeebasubs-ready data to pre-populate subscription form       
      */
     public static function mapJFBCProfileToAkeebasubs($profileData)
     {
         $countries = AkeebasubsHelperSelect::$countries;
         $states = AkeebasubsHelperSelect::$states;

         // To return
         $fbDataCheck = array();
         
         if(!is_object($profileData) || is_null($profileData))
            return $fbDataCheck;
        
         // location properties to check
         $locationFields = array('country', 'state', 'city');
        
         // Loop through all of the fbFields 'keys' to check/transform them for akeeba
         foreach($profileData as $f => $v){
            //If the field & value exist
            if($v){
                switch($f){
                        
                    //Location related object
                    case 'current_location':
                        foreach($locationFields as $l){
                                
                            //Check that the property exists in the location object & there's a value
                            if(property_exists($profileData->$f, $l) && $v){
                                switch($l){
                                    //Country property
                                    case "country": 
                                        $fbDataCheck[$l] = self::getLocationIdFromName('countries', $v->$l);
                                        break;
                                    
                                    //State property    
                                    case "state":
                                        // Check if the state exists in akeeba subs
                                        $state = self::getLocationIdFromName('states', $v->$l, $profileData->current_location->country);
                                        
                                        // set state to 2 char ISO code if getLocationIdFromName() not empty OR to original string from FB id 
                                        $fbDataCheck[$l] = (!is_null($state) && strlen($state) >= 3) ? $state : $v->$l;
                                        break;
                                    
                                    //Other properties     
                                    default:
                                        $fbDataCheck[$l] = $v->$l;
                                        break;
                                }// End switch $l
                            }// End if property_exists    
                        }// End foreach $locationFields
                        break;
                    
                    //DOB
                    case 'birthday':
                        $fbDataCheck['dob'] = date('m/d/Y',strtotime($v));
                        break;
                    
                    // GENDER
                    case 'sex':
                        $fbDataCheck['gender'] = self::getGenderIdFromString($v);
                        break;
                    
                    // Anything else    
                    default:
                        $fbDataCheck[$f] = $v;
                        break;
                }// End switch 
            }//End if
         }// End foreach
         /********************************REMOVE THIS******************************************/
		 //echo "<pre>Mapped Data<br />" . print_r($fbDataCheck, true) . "</pre>"; die();
         return $fbDataCheck;
     }
    
    /**
     * Method to get the gender ID from string
     * Transforms the 'male'/'female' strings from facebook
     * to populate the subscription form with relevant numeric ID
     * 
     * @param   string  $string     "male" or "female" - anything else returns 0
     * @return  int     $genderID   0 = not specified, 1 = male, 2 = female
     */
    public static function getGenderIdFromString($string)
    {
        $genderID = 0; //Default
        
        // Check there was a string passed in
        if(!is_string($string) || strlen($string) <= 1)
            return $genderID; // return the default
        
        switch($string){
            case "male":
                $genderID = 1;
                break;
            case "female":
                $genderID = 2;
                break;
            default:
                $genderID = 0;
                break;
            
        }
        // return ID
        return $genderID;
    }
    
    /**
     * Method to get the Country/State ID from name of the state/country
     * 
     * @param   string  $type       The type to get - 'countries' or 'states'
     * @param   string  $name       The name of the country/state
     * @param   string  $country    The name of country - ONLY used in case of state lookup
     * @return  sting   $returnID   Either 2 letter ISO Country/State code or nothing if not found
     */
    public static function getLocationIdFromName($type, $name, $country="")//, $locations
    {
        // Valid types to check against
        $types = array('countries', 'states');
                
        // To return
        $returnID = ""; // Default
        
        // Check we have values to search on
        if((!is_string($name) || strlen($name) <= 0) || 
            (!in_array($type, $types) || !is_string($type)))
            return $returnID;
            
        // Get the requested var from AkeebasubsHelper
        $locType = AkeebasubsHelperSelect::$$type;
        
        switch($type){
            case "countries":
                if(is_array($locType) && count($locType) > 0)
                    $returnID = (string)array_search($name, $locType);
                break;
                
            case "states":
                // Bail out if no country is passed or if no states present for the country
                if(is_string($country) && strlen($country) > 1 && array_key_exists($country, $locType)){
                    //$states = $this->$type;
                    $returnID = (string)array_search($name, $locType[$country]);
                }
                break;
                
            default:
                break;
        }
        
        // return
        return $returnID;
    }
}
I know that you're likely to be super busy but I'd really appreciate a quick response to this as this is currently preventing the site from going live.

Many thanks in advance,

Gez
The topic has been locked.
Support Specialist
Gez,
The first step would be to disable your plugin and make sure authentication still works as expected. That will narrow down if your plugin is preventing logging in, somehow, or if there's a bug somewhere else in the process. There was very little changed about the authentication process in this latest release, and the few changes were mainly for Google+ as well as the cleanup of a fatal error that only occurred when a user couldn't be saved (and the registration wouldn't have worked anyways).

So, test without your plugin and clear your Joomla and browser cache first. Test if that works, and let me know. From there, we can start narrowing down where to focus more.

Also, if you have Google+ authentication enabled, try with that (both with your plugin enabled and disabled) and let me know if that works or not as well. It will be another useful data point for investigation.

Thanks,
Alex
The topic has been locked.
Active Subscriptions:

None
10 years 7 months ago - 10 years 7 months ago #37343 by learnthrusong
Hi Alex, firstly thanks for your swift response!

OK, I'm finally getting somewhere with this (I think)!

[EDIT]
I have some custom akeeba subs template overrides, specifically default_facebook.php that renders a collapsible div containing the information fetched from Facebook. This serves to show the user the information more concisely since all of the pre-populated fields are hidden (in default_fields.php). It also displays the user's avatar and a customised welcome message for facebook users.

The issue that I have is that, where I have some JFBConnect related calls, I receive a fatal error. Clearly, this is because of a change in the plugin and jfbconnect libs, right? Here is the code:
$user = JFactory::getUser();

// JFBConnect Stuff
$userMapModel = new JFBConnectModelUserMap(); 
$fbUserId = $userMapModel->getFacebookUserId($user->id);
$fbProfileUrl = 'https://www.facebook.com/profile.php?id='.$fbUserId;  
$profileLibrary = JFBConnectProfileLibrary::getInstance(); 
$fbAvatarUrl = $profileLibrary->getAvatarUrl($userMapModel->getFacebookUserId($user->id));
I'm now getting an error stating that
Fatal error: Call to undefined method JFBConnectProfileLibrary::getInstance() in ... /public_html/templates/js_impacto/html/com_akeebasubs/level/default_facebook.php on line 74 that relates to $profileLibrary = JFBConnectProfileLibrary::getInstance(); . How should I be getting an instance of the profile library (i.e. assigning the $profileLibrary var) correctly in order to assign the $fbAvatarUrl? [/EDIT]

Many thanks,

Gez

P.S. I am now getting logged in ;)
Last edit: 10 years 7 months ago by learnthrusong.
The topic has been locked.
Support Specialist
Gez,
Update that code to:
$fbUserId = JFBCFactory::usermap()->getFacebookUserId($user->id); 
$fbProfileUrl = 'https://www.facebook.com/profile.php?id='.$fbUserId;  
$fbAvatarUrl = JFBCFactory::provider('facebook')->profile->getAvatarUrl($fbUserId));
I hope that helps, but if you run into any other issues, just let us know, and we'll gladly help!

Thanks,
Alex
The topic has been locked.
Active Subscriptions:

None
Awesome!

Thanks Alex!!!
The topic has been locked.
Active Subscriptions:

None
Hi Alex,

Unfortunately, I'm still having a few issues with this. When I replace the code with the 3 lines you provided, I'm getting a Fatal Error: Cannot access empty property in .../public_html/libraries/joomla/registry/registry.php on line 342.

The code:

alzander wrote:

$fbUserId = JFBCFactory::usermap()->getFacebookUserId($user->id); 
$fbProfileUrl = 'https://www.facebook.com/profile.php?id='.$fbUserId;  
$fbAvatarUrl = JFBCFactory::provider('facebook')->profile->getAvatarUrl($fbUserId));


Debugging this further, I discovered that the page still loads clearing the Fatal Error when I comment out the last line like:

alzander wrote:

//$fbAvatarUrl = JFBCFactory::provider('facebook')->profile->getAvatarUrl($fbUserId));[

. However, the first variable, $fbUserId is empty resulting in the $fbUserId being empty in the $fbProfileUrl. This is obviously part of the issue since the $fbUserId is used in the latter call to get the avatar url too.

Also, to be absolutely certain that the joomla user id was set correctly, I echoed that and got my user ID as expected.

So, to confirm:

alzander wrote:

$fbUserId = JFBCFactory::usermap()->getFacebookUserId($user->id); // results in empty
$fbProfileUrl = 'https://www.facebook.com/profile.php?id='.$fbUserId;  // results in 'https://www.facebook.com/profile.php?id='


To which file does the JFBCFactory class refer? I don't see any file names in the sourcecoast lib that matches. I have the following files:
  1. adminHelper.php
  2. articleContent.php
  3. easyTags.php
  4. openGraph.php
  5. openGrapghPlugin.php
  6. plugins/socialprofile.php
  7. profile.php
  8. &
  9. utilities.php
Thanks in advance for the wonderful support!!!

Gez
The topic has been locked.
Support Specialist
Gez,
Looks like there's a bug with our backward compatibility code in the latest release. Please update the $fbUserId line to:
$fbUserId = JFBCFactory::usermap()->getProviderUserId($user->id, 'facebook');
That should get the proper $fbUserId for later calls, but let me know if there are still issues when you do so.

The JFBCFactory class is defined in /components/com_jfbconnect/libraries/factory.php It's a simple helper to load up the proper Provider classes (Facebook, Google+) and their profile libraries. Using it is meant to reduce code and make it much simpler for us to add more social network providers in the future (Twitter, LinkedIn, etc).

I hope that helps,
Alex
The topic has been locked.
Active Subscriptions:

None
Excellent, that did the trick! ;)

Thanks Alex!!!
The topic has been locked.
Support Specialist
Glad to hear that got you going, and sorry for the silly bug on our part. It will be fixed in the next release, but you shouldn't use the getFacebookUserId code going forward anyways as thats the 'old' method.

Anyways, as always, should you need anything else, just let us know.

Thanks,
Alex
The topic has been locked.