Page 1 of 1

New FWH 25.01

Posted: Wed Feb 19, 2025 8:16 am
by Antonio Linares

Re: New FWH 25.01

Posted: Wed Feb 19, 2025 8:30 am
by hua
What are the minimum Windows version required to run programs created with FWH+[x]Hb nowadays?

Re: New FWH 25.01

Posted: Wed Feb 19, 2025 8:38 am
by Antonio Linares
Dear Hua,

Not sure if we still support Windows 7

Re: New FWH 25.01

Posted: Wed Feb 19, 2025 3:14 pm
by Jack
Hi
What about this :
We will provide support for the oAuth2 on the next version FWH
with samples to send e-mail using GMail/Outlook(Office365).

Thanks,

Philippe

Re: New FWH 25.01

Posted: Thu Feb 20, 2025 8:35 am
by Silvio.Falconi
Jack wrote: Wed Feb 19, 2025 3:14 pm Hi
What about this :
We will provide support for the oAuth2 on the next version FWH
with samples to send e-mail using GMail/Outlook(Office365).

Thanks,

Philippe
I think it is an initiative by laiton maybe antonio is not aware

Re: New FWH 25.01

Posted: Thu Feb 20, 2025 9:35 am
by Antonio Linares
Jack wrote: Wed Feb 19, 2025 3:14 pm Hi
What about this :
We will provide support for the oAuth2 on the next version FWH
with samples to send e-mail using GMail/Outlook(Office365).

Thanks,

Philippe
It is coming. We will publish an updated FWH 25.01 asap.

Re: New FWH 25.01

Posted: Sun Feb 23, 2025 4:31 pm
by JoséQuintas
My mix works with FWH 25.01

Many thanks.

Re: New FWH 25.01

Posted: Tue Feb 25, 2025 6:45 pm
by Lailton
Hello everyone,

An example of how Harbour + FWH works with OAuth.
Image

Here is a sample of the code.
Code Sample

Code: Select all | Expand

#include "fivewin.ch"

static oGmail, hStore

function main()

	local oDlg
	local oName, cName := ""
	local oEmail, cEmail := ""
	local oPhoto, oSend
	local oConnect, oDisconnect

	hStore := readStore( hb_dirBase() + "gmail.json" )

	oGmail := TGmail():new()

	oGmail:setConfig( {;
		"client_id" => "your_client_id",;
		"client_secret" => "your_client_secret",;
		"redirect_uri" => "http://localhost:2025/";
	} )

	if !empty( hStore[ "token" ] )
		oGmail:setToken( hStore[ "token" ] )
	endif

	define dialog oDlg resource "GMAIL"

		redefine image oPhoto id 4002 of oDlg
		redefine say oName var cName id 4003 of oDlg
		redefine say oEmail var cEmail id 4004 of oDlg

		redefine button oDisconnect id 4005 of oDlg action onDisconnect( oDlg, { oPhoto, oName, oEmail, oSend, oDisconnect }, { oConnect } )
		redefine button oSend id 4006 of oDlg action onSendMail()

		redefine button oConnect id 4001 of oDlg action onConnect( oDlg, { oPhoto, oName, oEmail, oSend, oDisconnect }, { oConnect } )

		oDlg:bStart := { || updateControls( oDlg, { oPhoto, oName, oEmail, oSend, oDisconnect }, { oConnect } ) }

		oDlg:lHelpIcon := .f.

	activate dialog oDlg centered

	saveStore( hb_dirBase() + "gmail.json", hStore )

return nil

function onConnect( oDlg, aConnect, aDisconnect )

	local cToken

	if !oGmail:isAuth()
		cToken := oGmail:auth()
		if !empty( cToken )
			hStore[ "token" ] := cToken
		else
			msgStop( "Authentication failed!" )
		endif
	endif

	updateControls( oDlg, aConnect, aDisconnect )

return nil

function onDisconnect( oDlg, aConnect, aDisconnect )

	local cProfile := hb_dirBase() + "profile_gmail.jpg"

	oGmail:revoke()
	updateControls( oDlg, aConnect, aDisconnect )

	if hb_vfExists( cProfile )
		hb_vfErase( cProfile )
	endif

return nil

function onSendMail()

	if oGmail:send( "lailton@paysoft.com.br", "it is a test", "<b>Message from Gmail oAuth2</b>", .t., {} )
		msgInfo( "Mail sent!" )
	else
		msgStop( "Failed to send email. You may not have authorized the required permissions..." )
	endif

return nil

function updateControls( oDlg, aConnect, aDisconnect )

	local hUser
	local cProfile := hb_dirBase() + "profile_gmail.jpg"

	if oGmail:isAuth()
		hUser := oGmail:me()
	endif

	aEval( aConnect, { |o| o:hide() } )
	aEval( aDisconnect, { |o| o:hide() } )

	if hb_isHash( hUser )

		aEval( aConnect, {|o|o:show(),o:refresh()} )

		if !hb_vfExists( cProfile )
			oGmail:downloadUrl( hUser[ "picture" ], cProfile )
		endif

		// Load Profile Photo
		if hb_vfExists( cProfile )
			aConnect[1]:loadImage(, cProfile )
			aConnect[1]:refresh()
		endif

		aConnect[2]:setText( hUser[ "name" ] )
		aConnect[3]:setText( hUser[ "email" ] )

		aConnect[2]:update()
		aConnect[3]:update()

	else

		aEval( aDisconnect, {|o|o:show(),o:refresh()} )

	endif

	oDlg:update()

return nil

function readStore( cFile )

	local hStore

	if hb_vfExists( cFile )
		hStore := hb_jsonDecode( hb_memoRead( cFile ) )
	endif

	if !hb_isHash( hStore )
		hStore := {;
			"token" => "";
		}
	endif

return hStore

function saveStore( cFile, hStore )

	hb_memoWrit( cFile, hb_jsonEncode( hStore ) )

return hb_vfExists( cFile )
It will be included in FiveWin today for the next version. :D

Re: New FWH 25.01

Posted: Tue Feb 25, 2025 6:51 pm
by vilian
Hi Lailton

Is TGMAIL a new class ?

Re: New FWH 25.01

Posted: Tue Feb 25, 2025 6:53 pm
by Lailton
vilian wrote: Tue Feb 25, 2025 6:51 pmTGmail
Yes, It is TGmail. on the next days I will add too the version for Office365 following same idea.

Re: New FWH 25.01

Posted: Wed Feb 26, 2025 8:25 am
by Silvio.Falconi
Lailton wrote: Tue Feb 25, 2025 6:53 pm
vilian wrote: Tue Feb 25, 2025 6:51 pmTGmail
Yes, It is TGmail. on the next days I will add too the version for Office365 following same idea.
I remember Cristobal made a Tgmail class some year ago

Have you tried this function ?

Make sure you have configured your application on Google Cloud Console and obtained the client_id, client_secret, and configured the redirect URL correctly.

Code: Select all | Expand


#include "FiveWin.ch"

FUNCTION SendEmail()
   LOCAL cUrl, cData, cResponse
   LOCAL cAccessToken := "YOUR_ACCESS_TOKEN"  // Get token via OAuth2
   LOCAL cMessage := '{"message": {"subject": "Test Email", "body": {"contentType": "Text", "content": "Hello, this is a test email!"}, "toRecipients": [{"emailAddress": {"address": "example@domain.com"}}]}}'

   cUrl := "https://graph.microsoft.com/v1.0/me/sendMail"
   cData := cMessage

   cResponse := HttpPostRequest(cUrl, cData, "Authorization: Bearer " + cAccessToken)
   
   IF !Empty(cResponse)
      MsgInfo("Email sent successfully!", "Success")
   ELSE
      MsgError("Failed to send email.", "Error")
   ENDIF

RETURN

Re: New FWH 25.01

Posted: Wed Feb 26, 2025 8:52 am
by Silvio.Falconi
This is my Tgmail class Use oAuth2

Code: Select all | Expand

CLASS Tgmail

   DATA cClientId // OAuth2 Client ID
   DATA cClientSecret // OAuth2 Client Secret
   DATA cRedirectUri // Redirect URI
   DATA cAccessToken // OAuth2 Access Token
   DATA cRefreshToken // OAuth2 Refresh Token
   DATA cAuthUrl // Authorization URL

   METHOD Init( cClientId, cClientSecret, cRedirectUri )
   METHOD GetAuthorizationUrl()
   METHOD GetAccessToken( cCode )
   METHOD SendEmail( cSubject, cBody, cRecipient )

ENDCLASS

// Constructor for Tgmail class
METHOD Init( cClientId, cClientSecret, cRedirectUri )
   ::cClientId := cClientId
   ::cClientSecret := cClientSecret
   ::cRedirectUri := cRedirectUri
   ::cAuthUrl := "https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/gmail.send&response_type=code&redirect_uri=" + SELF:cRedirectUri + "&client_id=" + SELF:cClientId
   RETURN NIL

// Method to get the authorization URL
METHOD GetAuthorizationUrl()
   RETURN ::cAuthUrl

// Method to get access token using authorization code
METHOD GetAccessToken( cCode )
   LOCAL cUrl, cData, cResponse, cTokenUrl
   LOCAL aJson
   cTokenUrl := "https://oauth2.googleapis.com/token"
   cData := "code=" + cCode + ;
            "&client_id=" + ::cClientId + ;
            "&client_secret=" + ::cClientSecret + ;
            "&redirect_uri=" + ::cRedirectUri + ;
            "&grant_type=authorization_code"

   // Request to get the token
   cResponse := HttpPostRequest( cTokenUrl, cData, "" )
   IF !Empty(cResponse)
      
      aJson := JsonParse( cResponse )
      ::cAccessToken := aJson[ "access_token" ]
      ::cRefreshToken := aJson[ "refresh_token" ]
      RETURN .T.
   ELSE
      RETURN .F.
   ENDIF

// Method to send an email via GMail
METHOD SendEmail( cSubject, cBody, cRecipient )
   LOCAL cUrl, cMessage, cRaw, cResponse

   // Prepare the message in MIME format
   cRaw := "From: 'me'\r\n" + ;
           "To: " + cRecipient + "\r\n" + ;
           "Subject: " + cSubject + "\r\n" + ;
           "Content-Type: text/plain; charset=UTF-8\r\n\r\n" + ;
           cBody

   // Encode the message in base64
   cRaw := Base64Encode( cRaw )

   // Build the request body
   cMessage := '{"raw": "' + cRaw + '"}'

   // URL to send the message
   cUrl := "https://gmail.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=multipart"

   // Send HTTP request with access token
   cResponse := HttpPostRequest( cUrl, cMessage, "Authorization: Bearer " + ::cAccessToken )
   IF !Empty(cResponse)
      RETURN .T.
   ELSE
      RETURN .F.
   ENDIF

RETURN NIL

SAMPLES
First of all, you need to create an object of the Tgmail class and initialize it with the authentication data.

Code: Select all | Expand

LOCAL oGmail
oGmail := Tgmail():New()
// Initialize with the client_id, client_secret and redirect_uri obtained from Google Console

Code: Select all | Expand

oGmail:Init( "YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET", "YOUR_REDIRECT_URI" )

You can get the authorization URL to visit to get the authorization code.

Code: Select all | Expand

LOCAL cAuthUrl
cAuthUrl := oGmail:GetAuthorizationUrl()
MsgInfo( "Visita questo URL per autorizzare l'app: " + cAuthUrl, "Autorizzazione OAuth2" )

Once the user has authorized the app and you have received the authorization code, you can get the access_token.

Code: Select all | Expand

LOCAL cCode, lSuccess
cCode := "AUTHORIZATION_CODE_OBTAINED_FROM_USER"  // Ottieni il codice di autorizzazione
lSuccess := oGmail:GetAccessToken( cCode )
IF lSuccess
   MsgInfo( "Token di accesso ottenuto!", "Successo" )
ELSE
   MsgError( "Errore durante l'ottenimento del token.", "Errore" )
ENDIF

Once you have the access_token, you can send an email.

Code: Select all | Expand

LOCAL lSuccess
lSuccess := oGmail:SendEmail( "Test Email", "Ciao, questa è una prova di invio email!", "recipient@example.com" )
IF lSuccess
   MsgInfo( "Email inviata con successo!", "Successo" )
ELSE
   MsgError( "Errore durante l'invio dell'email.", "Errore" )
ENDIF
You will need some auxiliary functions like HttpPostRequest and Base64Encode. Here is an example of how you could implement them:

// Function to perform an HTTP POST request

Code: Select all | Expand

FUNCTION HttpPostRequest( cUrl, cData, cHeaders )
   LOCAL cResponse
   cResponse := HttpPost( cUrl, cData, cHeaders )
   RETURN cResponse
//Function to encode in base64

Code: Select all | Expand

FUNCTION Base64Encode( cData )
   RETURN HB_BASE64ENCODE( cData )


This Tgmail class allows you to integrate with GMail via OAuth2, getting the access token and using it to send emails. Make sure you configure your application correctly on Google Cloud Console and manage the OAuth2 authorization flow.

Re: New FWH 25.01

Posted: Wed Feb 26, 2025 9:06 am
by Silvio.Falconi
My Office365 class

The Tgmail class I provided is specifically for integrating with the GMail API via OAuth2,
so it would not work directly for Office 365 (Outlook). However,

we can adapt it to also work with Office 365 (Microsoft Graph API),
which is the official API for interacting with Microsoft services, including Outlook and OneDrive.

Authentication via OAuth2:

To get the access_token, you will need to configure your application in the Azure Portal,
similar to how you did for GMail.
Microsoft Graph uses a similar authentication flow to GMail
but with different URLs and parameters.
Sending an email via Microsoft Graph:

The HTTP request to send the email will be directed to a different
URL than GMail, namely https://graph.microsoft.com/v1.0/me/sendMail.

Adapting the Tgmail class for Office 365 (Microsoft Graph API)
Below I show you how you can adapt the Tgmail class for Office 365.

The new class will be called Toffice365.

With this Toffice365 class, you can integrate your application with Office 365 (Microsoft Outlook) via OAuth2,
get an access_token,
and use it to send emails via the Microsoft Graph API.

Code: Select all | Expand

CLASS Toffice365

   DATA cClientId // OAuth2 Client ID
   DATA cClientSecret // OAuth2 Client Secret
   DATA cRedirectUri // Redirect URI
   DATA cAccessToken // OAuth2 Access Token
   DATA cRefreshToken // OAuth2 Refresh Token
   DATA cAuthUrl // Authorization URL

   METHOD Init( cClientId, cClientSecret, cRedirectUri )
   METHOD GetAuthorizationUrl()
   METHOD GetAccessToken( cCode )
   METHOD SendEmail( cSubject, cBody, cRecipient )

ENDCLASS

// Constructor for Toffice365 class
METHOD Init( cClientId, cClientSecret, cRedirectUri )
   ::cClientId := cClientId
   ::cClientSecret := cClientSecret
   ::cRedirectUri := cRedirectUri
   ::cAuthUrl := "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" + ;
                    "client_id=" + SELF:cClientId + ;
                    "&response_type=code" + ;
                    "&redirect_uri=" + SELF:cRedirectUri + ;
                    "&scope=Mail.Send"
   RETURN NIL

// Method to get the authorization URL
METHOD GetAuthorizationUrl()
   RETURN ::cAuthUrl

// Method to get access token using authorization code
METHOD GetAccessToken( cCode )
   LOCAL cUrl, cData, cResponse, cTokenUrl
   LOCAL aJson
   cTokenUrl := "https://login.microsoftonline.com/common/oauth2/v2.0/token"
   cData := "code=" + cCode + ;
            "&client_id=" + SELF:cClientId + ;
            "&client_secret=" + SELF:cClientSecret + ;
            "&redirect_uri=" + SELF:cRedirectUri + ;
            "&grant_type=authorization_code"

   // Request to get the token
   cResponse := HttpPostRequest( cTokenUrl, cData, "" )
   IF !Empty(cResponse)
      
      aJson := JsonParse( cResponse )
      ::cAccessToken := aJson[ "access_token" ]
      ::cRefreshToken := aJson[ "refresh_token" ]
      RETURN .T.
   ELSE
      RETURN .F.
   ENDIF

// Method to send an email via Office 365 (Microsoft Graph)
METHOD SendEmail( cSubject, cBody, cRecipient )
   LOCAL cUrl, cMessage, cRaw, cResponse

   // Prepara il messaggio in formato MIME
   cRaw := '{"message": {' + ;
           '"subject": "' + cSubject + '",' + ;
           '"body": {"contentType": "Text", "content": "' + cBody + '"},' + ;
           '"toRecipients": [{"emailAddress": {"address": "' + cRecipient + '"}}]' + ;
           '}}'

   // URL to send message via Microsoft Graph
   cUrl := "https://graph.microsoft.com/v1.0/me/sendMail"

   // Send HTTP request with access token
   cResponse := HttpPostRequest( cUrl, cRaw, "Authorization: Bearer " + SELF:cAccessToken )
   IF !Empty(cResponse)
      RETURN .T.
   ELSE
      RETURN .F.
   ENDIF

RETURN NIL


SAMPLES as tgmail samples

Code: Select all | Expand

LOCAL oOffice365
oOffice365 := Toffice365():New()

// Initializes with the client_id, client_secret and redirect_uri obtained from Azure Portal
oOffice365:Init( "YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET", "YOUR_REDIRECT_URI" )

Code: Select all | Expand

LOCAL cAuthUrl
cAuthUrl := oOffice365:GetAuthorizationUrl()
MsgInfo( "Visita questo URL per autorizzare l'app: " + cAuthUrl, "Autorizzazione OAuth2" )

Code: Select all | Expand

LOCAL cCode, lSuccess
cCode := "AUTHORIZATION_CODE_OBTAINED_FROM_USER"  // Codice di autorizzazione
lSuccess := oOffice365:GetAccessToken( cCode )
IF lSuccess
   MsgInfo( "Token di accesso ottenuto!", "Successo" )
ELSE
   MsgError( "Errore durante l'ottenimento del token.", "Errore" )
ENDIF

Code: Select all | Expand

LOCAL lSuccess
lSuccess := oOffice365:SendEmail( "Test Email", "Ciao, questa è una prova di invio email!", "recipient@example.com" )
IF lSuccess
   MsgInfo( "Email inviata con successo!", "Successo" )
ELSE
   MsgError( "Errore durante l'invio dell'email.", "Errore" )
ENDIF


Functions
// Funzione per eseguire una richiesta HTTP POST
FUNCTION HttpPostRequest( cUrl, cData, cHeaders )
LOCAL cResponse
cResponse := HttpPost( cUrl, cData, cHeaders )
RETURN cResponse