Forums / Developer / Forum (SMF), users integration, am I doing it right?

Forum (SMF), users integration, am I doing it right?

Author Message

Dimiter Ivanov

Wednesday 29 April 2009 4:52:49 am

Hello everyone, first of all congratulations on the great software!

I'm writing this post, to verify if what I am doing is the proper way of doing things, since I'm quite new to the open-source CMS systems, and ezpublish in particular.

eZ Publish is 4.0.0 and the Forum is SMF http://www.simplemachines.org/

The goal is to have the same user/password for ezpublish and the forum application.
Registrations are to be made only through ezpublish, and the user must be created on the forum automatically.

The site already has users, so I needed a way to 'export' them to the forum, and since the passwords are hashed, and SMF uses different algorithms for password hashing, I decided to export the user 'on-demand'.
When the user log-ins to ezpublish, and enters his login/password information, I use them to create the new user in SMF.

So, after reading through the documentations of both ezpublush and SMF, the ezpedia and searching this forum, i came up with the following:

1) Disable registration in the forum
2) "Intercept" user logins in ezpublish, and on successful login, create users in SMF (if they don't exist)
3) When user changes his password in ezpublish, update it in the forum and vice-versa.

<b>Part 1:</b>
disabling registrations is quite simple, the forum supports this feature.

<b>Part 2</b>, the "intercept".
I created a login_handler, and used the code for the <i>loginUser()</i> method, from <i>eZUser</i> class.
only added few lines in it:
- to verify if user exists in the SMF's database and create it, if not
- log-in the user in SMF
(all operations for SMF, are made using its API)

But to make this work, I need to make my login handler 'the default', so what i did was,
to 'reset' the <i>LoginHandler</i> array in <i>site.ini.append.php</i>:

[UserSettings]
LoginHandler[]
LoginHandler[]=myloginhandler

Which works, until you click something in the admin interface, that changes the ini file (toggle debuging output for example), when you do that, when ezpublish regenerates the ini file, it does not save the part where I reset the handlers array, and so my handler is never called...

Because of this, I guess it is not how its suppose to be done?

<b>Part 3</b>
- User edits his password.
following this: http://serwatka.net/en/blog/ez_publish_3_8_new_custom_edit_handler
I created a custom edit handler, but I needed the password the user entered, so I guessed that this information is only available in the <i>fetchInput()</i> method, which unfortunately is not discussed in that blog post.
By using the <i>eZDebug::writeDebug()</i> I came to the following:
it will be simpler to just paste what i did:

function fetchInput( $http, &$module, &$class, $object, &$version, $contentObjectAttributes, $editVersion, $editLanguage, $fromLanguage )
{
 $isValid=true; //flag to check if the user input is valid
 $objId=0; // will hold the object ID of the 'ezuser' datatype
 // only when Publishing class User
 if($class->attribute('id')==4 AND $module->isCurrentAction('Publish'))
 {
  // iterate through all submitted attributes to check if all data is correct
  // is this the proper way, or there is a single flag saying if all data is valid?
  foreach($contentObjectAttributes AS $obj)
  {
   if($obj->hasValidationError())
   {
    $isValid=false;
    break; // it's enough to have only 1 invalid attribute, to invalidate all input
   }
   if($obj->attribute("data_type_string")=="ezuser")
   {
    // this is needed to get the user and passwords from the POST
   $objId = $obj->attribute('id'); 
   }
  }
  // update the forum database, only if data is valid
  if($isValid)
  {
   // saw this check from 
   // kernel/classes/datatypes/ezuser/ezusertype.php
   // only instead of ContentObjectAttribute, there is a variable $base
   // which here is not available.
   if($http->hasPostVariable( "ContentObjectAttribute_data_user_login_" . $objId ) )
   {
     $login=$http->postVariable( 
     "ContentObjectAttribute_data_user_login_" . $objId );
     $password=$http->postVariable( 
     "ContentObjectAttribute_data_user_password_" . $objId );
     $passwordConfirm=$http->postVariable(
     "ContentObjectAttribute_data_user_password_confirm_" . $objId );
     $email=$http->postVariable( 
     "ContentObjectAttribute_data_user_email_" . $objId );
      // here I include the forum's API file, and update the password
      // ...
    }
   }		 
 }
}

This works like a charm, the only thing, I'm not sure here, is about the iteration over the <i>$contentObjectAttributes</i> to check if they are all valid.

But... this handler is only called when user edits his profile, from the <i>content</i> module. The user module also provides it's own 2 interfaces that change passwords
<i>/user/password</i> and <i>/user/forgotpassword</i>
I will keep it short, here because this post is already too long...
What i did is, copied the user module from the kernel, into my extension, and edited the files for those views, then I used "URL wildcard translation", to make the
<i>user/password/*</i> point to <i>myuser/password/{1}</i>. I also needed to set the correct permissions for the "password" function of my module.

And that I think is all I need to keep the password information in sync with SMF, when changed from eZ Publish.

Because of the problems with the order in which the login_handlers are called, I wonder if I should also 'override' the <i>/user/login</i> view, instead of using login_handler, for what I'm trying to do?

Now comes the other part, updating passwords in eZ Publish, when changed from SMF.

Creating and editing users in SMF within eZ Publish was quite easy, because their API is a single .php file, that you include and then call the functions.

How do I use the eZ Publish API now?
The forum and eZ Publish installations are on the same server.

Phew, that was longer than I expected, hope it makes sense... and thanks to anyone that read it :)

Dimiter Ivanov

Thursday 07 May 2009 1:00:59 pm

Hi again.

My extension for adding users to the forum, and updating passwords/email information works fine, but now, i can't figure out how to do it the other way around.
I want when the user changes his password from the forum software, to update it in ezpublish's database.
What is the proper way to do it and how?
Can I include some file from ezpublish, and make the API calls from there, or the only way to do it is with eZScript (for wich i need CLI access)?
Or simply i should 'hack' the database and change the password myself?

Any pointers appreciated.

André R.

Friday 08 May 2009 12:41:34 am

You can take a look in the eZ Publish views to see how the api is used, in this case in user/password aka kernel/user/password.php.

eZ Online Editor 5: http://projects.ez.no/ezoe || eZJSCore (Ajax): http://projects.ez.no/ezjscore || eZ Publish EE http://ez.no/eZPublish/eZ-Publish-Enterprise-Subscription
@: http://twitter.com/andrerom

Dimiter Ivanov

Friday 08 May 2009 1:34:49 am

André, my problem is using the API 'outside' of eZPubluish.

Is it possible to use the API ( fetch user object, and alter it and store it) within another application?

For example, I created a clean .php file, and tried to fetch user object with the API, change the password and store it. The file is in the eZPublish root folder, and to make it work I had to include autoload.php and access.php, to set the correct access.
It works, I can change the user object when I load the page from the browser, but when I Include this file from other directory, outside of the eZPublish root, even after I add the eZPublish root folder to the php include_path, it still crashes with errors.

Jianjun Hu

Saturday 09 May 2009 5:43:31 pm

I'm interested in this topic.

OnlyBlue

☆..·°∴°.☆°°.☆°.
°∴ °☆ .·enjoy star° .·★°∴°
∴°.°★ .·°
  ミ☆°∴°.★☆° ∴·°
°.☆° .·∴° 

Is it a pleasure after all to practice in due time what one has learnt?

Kristof Coomans

Wednesday 13 May 2009 12:09:52 pm

Hi Dimiter

To me, using a web service looks like a good solution to the problem you are facing.
After all, you want to let 2 completely disconnected applications communicate with each other.

Did you have a look at the eZ Publish built-in SOAP support, or at the NuSOAP extension ( http://projects.ez.no/nusoap )?
Good luck!

independent eZ Publish developer and service provider | http://blog.coomanskristof.be | http://ezpedia.org

Dimiter Ivanov

Wednesday 13 May 2009 3:29:20 pm

Well, the applications are not <i>that</i> disconected, since they both live in the same filesystem, and database server, soap maybe is a good general solution, i will look it up..

But here is what i cam up with, the solution (as it allways happens) is much more simpler than i was expecting.

Setting, the <b>current working dir</b> for the .php file did the trick.
here is the code that i used, to 'initialize' the eZPublish environment.
this sits in a 'clean', 'empy' .php file:

$path="/full/path/to/ezpublish/root";
//the magick trick
chdir($path);
//these files sit in the eZPublish root 
require ('autoload.php');
require_once("access.php");

/*!
  Dummy function, required by some scripts in eZ Publish.
*/
function eZUpdateDebugSettings( $useDebug = null )
{
}
//create eZINI instance providing the root folder for the settings files
$ini = eZINI::instance("site.ini",$path . "/settings",null,false);

$siteaccess = $ini->variable( 'SiteSettings', 'DefaultAccess' );

$access = array( 'name' => $siteaccess,
				 'type' => EZ_ACCESS_TYPE_DEFAULT );

$access = changeAccess( $access );
// here ends the eZPublish 'init'


// I omit some required SMF defines

//from here on, this (what i needed) works:

function integrate_reset_pass($old_username, $username, $password)
{	
	$user= eZUser::fetchByName($old_username,true);
	if($user)
	{
		$site=$user->site();
		$type = $user->attribute( "password_hash_type" );
		$newHash = $user->createHash( $old_username, $password, $site, $type );
		$user->setAttribute( "password_hash", $newHash );
		$user->store();
	}
}
function integrate_change_member_data($members, $var, $data)
{	
	if($var=="emailAddress")
	{
		$user= eZUser::fetchByName($members[0],true);
		if($user)
		{			
			$user->setAttribute( "email", trim($data,"'") );
			$user->store();
		}
	}
}

When this file is included in the SMF forum's index.php file, the forum will call the integrate_* functions that i have defined and that's it.

I don't know if this is a proper way to do it, but form what i have tested it works.