*&---------------------------------------------------------------------*
*& Report  Z_PASSWORD_MAILER                                           *
*& Creation: 22.01.2021  Shortcut IT GmbH                              *
*&---------------------------------------------------------------------*
*& Simple password mailer.                                             *
*& Use it to send a mail with password information to lots of users.   *
*& You can find some more information in this blog article:            *
*& https://www.shortcut-it.com/blog/index.php?how-to-assign-new-       *
*&    passwords-to-lots-of-users                                       * 
*& It requests an input file from the function "Set Password" (created *
*& using the "View details" button > "Export to file").                *
*& For each user an email will be sent containing the new password.    *
*&---------------------------------------------------------------------*
report Z_PASSWORD_MAILER.
* <selection screen>
* F4 for selecting the client.
data: f4Client like t000-mandt.
* For putting pTxtLn1..20 on the selection screen for the email body.
types: TBodyLine type string.
define BODYTEXT.
   selection-screen begin of line.
    selection-screen comment (1) ml&1 for field pTxtln&1.
    parameters: pTxtln&1 type TBodyLine lower case.
  selection-screen end of line.
end-of-definition.
* Specify the input file
selection-screen pushbutton /1(70) pbFile user-command PB1
                                       modif id MB1.
* This is for indicating the amount of records in the given file:
selection-screen begin of line.
  selection-screen comment (50) txt_l for field pdummy.
  parameters: pdummy type c no-display.
selection-screen end of line.
* Specification of SID and client
*   (the login data could be of course for another SID / client)
selection-screen skip 1.
selection-screen begin of line.
  selection-screen comment (20) txt_1 for field pSID.
  parameters: pSID type sy-sysid obligatory default sy-sysid.
selection-screen end of line.
selection-screen begin of line.
  selection-screen comment (20) txt_2 for field pClient.
  parameters: pClient like f4Client obligatory default sy-mandt.
selection-screen end of line.
* Email title
selection-screen skip 1.
selection-screen begin of line.
  selection-screen comment (20) txt_3 for field pTitle.
  parameters: pTitle type so_obj_nam lower case obligatory.
selection-screen end of line.
* Email description (subject)
selection-screen begin of line.
  selection-screen comment (20) txt_4 for field pDescr.
  parameters: pDescr type so_obj_des lower case obligatory.
selection-screen end of line.
* Content of the Email (body)
selection-screen skip 1.
selection-screen begin of line.
  selection-screen comment (50) txt_5 for field pdummy2.
  parameters: pdummy2 type c no-display.
selection-screen end of line.
constants: numLines type i value 20.
* Using the macro we will get fields pTxtln1..20 for the
*   email body.
BODYTEXT: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
          11, 12, 13, 14, 15, 16, 17, 18, 19, 20.
* </selection screen>
" The input file (csv format)
data: fileName type string.
" Structure of the records in the CSV file.
types: begin of CsvRecord,
         user type xubname,
         userType type xuustyp,
         group type xuclass,
         validFrom type char10,
         validTo type char10,
         newPassword type string,
         accnt type xuaccnt,
         costCenter type xukostl,
         lang type xulangu,
         email type ad_smtpadr,
         title type AD_TITLETX,
         firstName type AD_NAMEFIR,
         lastName type AD_NAMELAS,
         fullName type ad_namtext,
         department type AD_DPRTMNT,
         function type ad_fnctn,
         building type ad_bldng_p,
         floor type ad_floor,
         room type ad_roomnum,
         country type land1,
         city type ad_city1,
         street type ad_street,
         company type uscomp,
       end of CsvRecord.
" Structure of the result list in ALV.
types: begin of ALVRecord,
         icon type icon_d,
         sendInfo type string,
         mailTitle type SO_OBJ_DES,
         user type xubname,
         fullName type ad_namtext,
         email type ad_smtpadr,
         newPassword type string,
       end of ALVRecord.
" For verifying the input file: check if the input file has a
"   header line starting like this:
data: headerLineStart type char80
      value 'User;User type;Group;Valid from;Valid to;New password'.
" A single record of the CSV file and a table with all of them.
data: userData type CsvRecord,
      allUserData type standard table of CsvRecord.
" A result list, to be shown in ALV.
data: alvRecord type ALVRecord,
      alvRecords type standard table of ALVRecord,
      successfulCnt type i,
      errorCnt type i.
" We use CL_RSDA_CSV_CONVERTER to convert a read line with CSV
"   format into the needed structure.
data: csvFileConverter type ref to CL_RSDA_CSV_CONVERTER,
" This is the output of the file open dialog (the filename)
      csvFileTable type filetable,
" This is the content of the file
      csvFileData type standard table of char2048.
* ======================================================================
at selection-screen output.
  perform initialization.
at selection-screen.
" Ready?
  case sy-ucomm.
  " Push button for uploading a file from the frontend
    when 'PB1'.
      perform uploadFile.
  " Execution is possible if a correct file was uploaded.
    when 'ONLI' or 'PRIN'.
      if ( lines( allUserData ) = 0 ).
          message e305(sdx).
      endif.
  " Execution in background not possible, we need the file from the
  "   frontend.
    when 'SJOB'.
      message e008(spads) with sy-repid.
  endcase.
* ======================================================================
" Ok, let's start.
start-of-selection.
  clear alvRecords.
" Send the emails.
  loop at allUserData into userData.
    perform sendMail.
  endloop.
" Show result list
  perform showResults.
* ===  That's all  ===
* ======================================================================
*  Upload of the input file. First select it from the frontend,
*    then read the file and checks whether the format is correct.
* ======================================================================
form uploadFile.
  data: foRc type sysubrc,
        foAction type i,
        winTitle type string.
  winTitle = 'Select csv file from ''Set Password'' function'.
  CALL METHOD cl_gui_frontend_services=>file_open_dialog
    EXPORTING
      window_title      = winTitle
      default_extension = 'csv' "#EC NOTEXT
      default_filename  = '*.csv' "#EC NOTEXT
      file_filter       = '(*.csv)|*.csv (comma separated file)'
    CHANGING
      file_table        = csvFileTable
      rc                = foRc
      user_action       = foAction
    EXCEPTIONS
      FILE_OPEN_DIALOG_FAILED = 1
      CNTL_ERROR              = 2
      ERROR_NO_GUI            = 3
      NOT_SUPPORTED_BY_GUI    = 4
      others                  = 5.
  if ( sy-subrc <> 0 ).  " Error in file open dialog.
    message E129(SWF_HTTP).
  endif.
  " No file selected?
  if ( ( filename is initial ) and
       ( lines( csvFileTable ) = 0 ) ).
     message E582(RA).
  endif.
  if lines( csvFileTable ) = 0.
    exit.
  endif.
  " Now upload the data.
  fileName = csvFileTable[ 1 ].
  CALL FUNCTION 'GUI_UPLOAD'
    EXPORTING
      FILENAME                      = fileName
      FILETYPE                      = 'ASC'
    TABLES
      DATA_TAB                      = csvFileData
    EXCEPTIONS
      FILE_OPEN_ERROR               = 1
      FILE_READ_ERROR               = 2
      NO_BATCH                      = 3
      GUI_REFUSE_FILETRANSFER       = 4
      INVALID_TYPE                  = 5
      NO_AUTHORITY                  = 6
      UNKNOWN_ERROR                 = 7
      BAD_DATA_FORMAT               = 8
      HEADER_NOT_ALLOWED            = 9
      SEPARATOR_NOT_ALLOWED         = 10
      HEADER_TOO_LONG               = 11
      UNKNOWN_DP_ERROR              = 12
      ACCESS_DENIED                 = 13
      DP_OUT_OF_MEMORY              = 14
      DISK_FULL                     = 15
      DP_TIMEOUT                    = 16
      OTHERS                        = 17.
  if sy-subrc <> 0.   " Hmm, something went wrong.
    message E169(RSOD) with fileName sy-subrc.
  endif.
  field-symbols: <line> type char2048.
  " Iterate the lines in the file and convert them
  "   into the needed structure.
  loop at csvFileData assigning <line>.
    csvFileConverter->csv_to_structure(
      EXPORTING
        i_data   = <line>
      IMPORTING
        e_s_data = userData ).
    if ( sy-tabix = 1 ).  " This line has to contain the column titles.
      if ( not <line> cs headerLineStart or sy-fdpos <> 0 ).
        message e857(td) with fileName.  " File has invalid format
      endif.
      clear allUserData.   " Clear the table with the user data.
    else.
      append userData to allUserData.
    endif.
  endloop.
  " Does the file contain valid data?
  if ( lines( allUserData ) = 0 ).
    message e389(s#).
  endif.
  " Ok, file is ok. Put the filename into the push button and
  "   write information about the amount of data in it on
  "   the selection screen.
  concatenate ICON_BIW_SOURCE_SYS_FILE fileName into pbFile.
  txt_l = lines( allUserData ).
  condense txt_l no-gaps.
  concatenate 'Records in file:' txt_l into txt_l separated by space.
endform.
* ======================================================================
*  Send the Email via SO_NEW_DOCUMENT_SEND_API1
* ======================================================================
form sendMail.
  data: emailAttr type sodocchgi1,
        emailReceiver type somlreci1,
        emailReceivers type standard table of somlreci1,
        emailBody type standard table of char255,
  " A text variable and field symbol to address the textlnxx fields
        tFname(8) type c,
        txtLine type TBodyLine.
  field-symbols: <textln> type TBodyLine.
  " Fill the fields for the result list.
  move-corresponding userdata to alvRecord.
  alvRecord-icon = ICON_FAILURE.
  if ( userData-email is initial ).
    alvRecord-sendInfo = 'No email address given for this user.'.
    append alvRecord to alvRecords.
    add 1 to errorCnt.
    exit.
  endif.
  " Set Email attributes and receiver.
  emailAttr-obj_name = pTitle.
  emailAttr-obj_descr = pDescr.
  emailAttr-obj_langu = sy-langu.
  emailAttr-sensitivty = 'P'.
  emailAttr-obj_prio =  '1'.
  emailAttr-no_change = 'X'.
  emailAttr-priority = '1'.
  emailReceiver-receiver = userData-email.
  emailReceiver-rec_type = 'U'.
  emailReceiver-express = 'X'.
  append emailReceiver to emailReceivers.
  do numLines times.
    tFname = sy-index.
    condense tFname no-gaps.
    concatenate 'PTXTLN' tFname into tFname.
    assign (tFname) to <textln>.
    txtLine = <textln>.
    perform replPlaceHolders using txtLine.
    append txtLine to emailBody.
  enddo.
  perform replPlaceHolders using emailAttr-obj_name.
  perform replPlaceHolders using emailAttr-obj_descr.
  call function 'SO_NEW_DOCUMENT_SEND_API1'
       exporting
            document_data  = emailAttr
            document_type  = 'RAW'
            put_in_outbox  = 'X'
       tables
            object_content = emailBody
            receivers      = emailReceivers
       exceptions
            TOO_MANY_RECEIVERS = 1
            DOCUMENT_NOT_SENT  = 2
            DOCUMENT_TYPE_NOT_EXIST = 3
            OPERATION_NO_AUTHORIZATION = 4
            PARAMETER_ERROR = 5
            X_ERROR = 6
            ENQUEUE_ERROR = 7.
  alvRecord-mailTitle = emailAttr-obj_descr.
  if ( sy-subrc = 0 ).
    alvRecord-icon = ICON_OKAY.
    alvRecord-sendInfo = 'Mail sent successfully'.
    add 1 to successfulCnt.
  else.
    case sy-subrc.
      when 1. alvRecord-sendInfo = 'TOO_MANY_RECEIVERS'.
      when 2. alvRecord-sendInfo = 'DOCUMENT_NOT_SENT'.
      when 3. alvRecord-sendInfo = 'DOCUMENT_TYPE_NOT_EXIST'.
      when 4. alvRecord-sendInfo = 'OPERATION_NO_AUTHORIZATION'.
      when 5. alvRecord-sendInfo = 'PARAMETER_ERROR'.
      when 6. alvRecord-sendInfo = 'X_ERROR'.
      when 7. alvRecord-sendInfo = 'ENQUEUE_ERROR'.
    endcase.
    concatenate 'Sending mail failed. SY-SUBRC: ' alvRecord-sendInfo
        into alvRecord-sendInfo.
    add 1 to errorCnt.
  endif.
  append alvRecord to alvRecords.
  commit work.
endform.
* ======================================================================
*  Replace the placeholders with the given values.
* ======================================================================
form replPlaceHolders using pText.
    replace all occurrences of:
         '&SID' in pText with pSID,
         '&client' in pText with pClient,
         '&user' in pText with userdata-user,
         '&userType' in pText with userdata-userType,
         '&group' in pText with userdata-group,
         '&validFrom' in pText with userdata-validFrom,
         '&validTo' in pText with userdata-validTo,
         '&newPassword' in pText with userdata-newPassword,
         '&accnt' in pText with userdata-accnt,
         '&costCenter' in pText with userdata-costCenter,
         '&lang' in pText with userdata-lang,
         '&email' in pText with userdata-email,
         '&title' in pText with userdata-title,
         '&firstName' in pText with userdata-firstName,
         '&lastName' in pText with userdata-lastName,
         '&fullName' in pText with userdata-fullName,
         '&department' in pText with userdata-department,
         '&function' in pText with userdata-function,
         '&building' in pText with userdata-building,
         '&floor' in pText with userdata-floor,
         '&room' in pText with userdata-room,
         '&country' in pText with userdata-country,
         '&city' in pText with userdata-city,
         '&street' in pText with userdata-street,
         '&company' in pText with userdata-company.
endform.
* ======================================================================
*  Show the result list.
* ======================================================================
form showResults.
  data: alvTable  type ref to cl_salv_table,     " The ALV table
        alvHeader type ref to cl_salv_form_element,   " header element
        alvGridLayout type ref to cl_salv_form_layout_grid,
        alvLabel type ref to cl_salv_form_label,
        alvText type ref to cl_salv_form_text,
        exSALVMsg  type ref to cx_salv_msg,
        alvCol type ref to cl_salv_columns,
        exSALVNotFound type ref to cx_salv_not_found.
  try.
    cl_salv_table=>factory(
      importing
        r_salv_table = alvTable
      changing
        t_table = alvRecords ).
     alvTable->get_functions( )->set_all( abap_true ).
     alvTable->get_display_settings( )->set_list_header(
         'Password mailer' ).
     alvTable->get_display_settings( )->set_striped_pattern(
         cl_salv_display_settings=>true ).
     alvCol = alvTable->get_Columns( ).
     alvCol->set_optimize( abap_true ).
     alvCol->get_column( 'SENDINFO' )->set_long_text( 'Result' ).
     alvCol->get_column( 'NEWPASSWORD' )->set_long_text(
         'New password' ).
     " Put some info about the results in top-of-page and show
     "   the list.
     create object alvGridLayout.
     alvGridLayout->create_header_information(
       EXPORTING row = 1 column = 1
         text = 'Result of mail send process' ).
     alvGridLayout->add_row( ).
     alvLabel = alvGridLayout->create_label(
       EXPORTING
         row = 2 column = 1 text = 'Mails sent successfully:' ).
     alvText = alvGridLayout->create_text(
         row = 2 column = 2
         text = successfulCnt ).
     alvLabel->set_label_for( alvText ).
     alvLabel = alvGridLayout->create_label(
       EXPORTING
         row = 3 column = 1 text =  'Errors:' ).
     alvText = alvGridLayout->create_text(
         row = 3 column = 2 text = errorCnt ).
     alvLabel->set_label_for( alvText ).
     alvHeader = alvGridLayout.
     alvTable->set_top_of_list( alvHeader ).
     alvTable->display( ).
  catch cx_salv_msg into exSALVMsg.
    message exSALVMsg->get_text( ) type 'E'.
  catch cx_salv_not_found into exSALVNotFound.
    message exSALVNotFound->get_text( ) type 'E'.
  endtry.
endform.
* ======================================================================
*  Some initialization stuff, to be done once only.
* ======================================================================
form initialization.
  statics: initDone type abap_bool.
  if ( initDone = abap_true ).
    exit.
  endif.
  " Some texts for the selection screen
  concatenate ICON_BIW_SOURCE_SYS_FILE 'Select input file' into pbFile.
  txt_l = 'no file specified yet'.
  txt_1 = 'System ID'.
  txt_2 = 'Client'.
  txt_3 = 'Login info'.
  pTitle = 'Login info'.
  txt_4 = 'Email description:'.
  pDescr = 'Your login data for system &SID, client &client'.
  txt_5 = 'Email body (with placeholders):'.
  pTxtln1  = 'Dear &title &firstName &lastName,'.
  pTxtln2  = ''.
  concatenate 'find in this email your login data for'
      'system &SID, client &client.' into pTxtln3 separated by space.
  pTxtln4  = ''.
  pTxtln5  = 'Your user ID: &user'.
  pTxtln6  = 'Your initial password: &newPassword'.
  pTxtln7  = ''.
  concatenate 'With the first login you usually have to assign'
      'a new password.' into pTxtln8 separated by space.
  concatenate 'If you lose your password, you can use this link'
      'to get a new one:' into pTxtln9 separated by space.
  pTxtln10 = '(a link to a user self-service for a new password)'.
  pTxtln11 = ''.
  pTxtln12 = 'Kind regards, your SAP IT service.'.
  " Use the CL_RSDA_CSV_CONVERTER for conversion of the read records
  csvFileConverter = cl_rsda_csv_converter=>create(
      i_delimiter = '"' i_separator = ';' ).
  initDone = abap_true.
endform.