Version: current

      1 <?php
      2
      3 /**
      4  * Displays a field for importing recipients.
      5  *
      6  * @package newsletter
      7  */
      8 class RecipientImportField extends FormField {
      9
     10     protected $memberGroup;
     11     protected $memberClass;
     12     protected $tableColumns;
     13     protected $table;
     14     protected $clientFileName;
     15     protected $typeID;
     16
     17     static $column_types = array(
     18         'Salutation' => array( 'title', 'salutation' ),
     19         'FirstName' => array( 'firstname', 'christianname', 'givenname' ),
     20         'Surname' => array( 'lastname','surname', 'familyname' ),
     21         'Email' => array( 'email', 'emailaddress' ),
     22         'Address' => array( 'address' ),
     23         'PhoneNumber' => array('phone','phonenumber'),
     24         'JobTitle' => array( 'jobtitle' ),
     25         'Organisation' => array( 'organisation', 'organization' ),
     26         'EmailType' => array( 'htmlorplaintext', 'emailtype' )
     27     );
     28
     29     static $custom_set_fields = array();
     30
     31     public static function setCustomField( $getMapCode, $fieldName ) {
     32         self::$custom_set_fields[] = array(
     33             'Code' => $getMapCode,
     34             'Field' => $fieldName
     35         );
     36     }
     37
     38     function __construct( $name, $title, $memberGroup, $memberClass = 'Member', $form = null ) {
     39         $this->memberGroup = $memberGroup;
     40         $this->memberClass = $memberClass;
     41         parent::__construct( $name, $title, null, $form );
     42     }
     43
     44     function Field() {
     45         $frameURL = Director::absoluteBaseURL() . 'admin/newsletter/displayfilefield/' . $this->typeID;
     46
     47         return "<iframe name=\"{$this->Name}\" frameborder=\"0\" class=\"RecipientImportField\" src=\"$frameURL\"></iframe>";
     48     }
     49
     50     function setTypeID( $id ) {
     51         $this->typeID = $id;
     52     }
     53
     54     function CustomSetFields() {
     55         $fields = new FieldSet();
     56
     57
     58
     59         foreach( self::$custom_set_fields as $customField ) {
     60             eval( '$map = ' . $customField['Code'] .';' );
     61
     62             $noneMap = array( 0 => '(Not set)' );
     63
     64             $map = $noneMap + $map;
     65
     66             $fields->push( new DropdownField( 'Set['.$customField['Field'].']', $customField['Field'], $map ) );
     67         }
     68
     69         return $fields;
     70     }
     71
     72     /**
     73      * Returns HTML to be displayed inside the IFrame
     74      */
     75     static function fileupload() {
     76
     77     }
     78
     79     /**
     80      * Returns the table of results to be displayed in the table of
     81      * details loaded from the file
     82      */
     83     function displaytable() {
     84
     85         // Check that a file was uploaded
     86         $tempFile = fopen( $_FILES['ImportFile']['tmp_name'], 'r' );
     87
     88       // display some error if the file cannot be opened
     89         if(!$tempFile) {
     90           return 'The selected file did not arrive at the server';
     91         }
     92
     93         $this->clientFileName = $_FILES['ImportFile']['name'];
     94
     95         while( ( $row = fgetcsv( $tempFile ) ) !== false ) {
     96
     97             if( !$this->tableColumns ) {
     98                 $this->parseTableHeader( $row );
     99             } else {
    100                 $newRow = array();
    101                 $newSessionTableRow = array();
    102
    103                 foreach( $row as $cell ) {
    104                     $newRow[] = $this->parseCSVCell( $cell );
    105                     $newSessionTableRow[] = $cell;
    106                 }
    107
    108                 $cells = new DataObjectSet( $newRow );
    109                 $table[] = $cells->customise( array( 'Cells' => $cells ) );
    110
    111                 $sessionTable[] = $newSessionTableRow;
    112             }
    113         }
    114
    115         fclose( $tempFile );
    116
    117         $this->table = new DataObjectSet( $table );
    118
    119         Session::set("ImportFile.{$_REQUEST['ID']}", $sessionTable);
    120
    121         return $this->renderWith( 'Newsletter_RecipientImportField_Table' );
    122     }
    123
    124     /**
    125      * Determines what type each column is
    126      */
    127     function parseTableHeader( $columns ) {
    128
    129         $columnSource = array_combine(
    130             array_keys( self::$column_types ),
    131             array_keys( self::$column_types )
    132         );
    133
    134         $columnSource = array_merge( array( 'Unknown' => 'Unknown' ), $columnSource );
    135         $colCount = 0;
    136         foreach( $columns as $cell ) {
    137             $columnType = $this->getColumnType( $this->parseCSVCell( $cell )->Value() );
    138             $this->tableColumns[] = new DropdownField( 'ImportFileColumns[' . (string)( $colCount++ ) . ']', '', $columnSource, $columnType );
    139         }
    140     }
    141
    142     function parseCSVCell( $cell ) {
    143         return new RecipientImportField_Cell( $cell );
    144     }
    145
    146     function getColumnType( $cell ) {
    147         $cell = strtolower( $cell );
    148         $escapedValue = preg_replace( '/[^a-z]/', '', $cell );
    149
    150         foreach( self::$column_types as $type => $aliases ) {
    151             if( in_array( $escapedValue, $aliases ) )
    152                 return $type;
    153         }
    154
    155         return 'Unknown';
    156     }
    157
    158     function setController( $controller ) {
    159         $this->controller = $controller;
    160     }
    161
    162     /**
    163      * Set of table column headers
    164      */
    165     function ColumnHeaders() {
    166         return new DataObjectSet( $this->tableColumns );
    167     }
    168
    169     function Rows() {
    170         return $this->table;
    171     }
    172
    173     function FileName() {
    174         return $this->clientFileName;
    175     }
    176
    177     function TypeID() {
    178         return $this->typeID;
    179     }
    180 }
    181
    182 /**
    183  * Single cell of the recipient import field
    184  *
    185  * @package newsletter
    186  */
    187 class RecipientImportField_Cell extends ViewableData {
    188     protected $value;
    189
    190     function __construct( $value ) {
    191         $this->value = $value;
    192         parent::__construct();
    193     }
    194
    195     function Value() {
    196         return $this->value;
    197     }
    198 }
    199
    200 /**
    201  * Upload form that appears in the iframe
    202  * @package cms
    203  * @subpackage newsletter
    204  */
    205 class RecipientImportField_UploadForm extends Form {
    206     function import( $data, $form ) {
    207         $id = $data['ID'];
    208         $mailType = DataObject::get_one("NewsletterType", "ID = $id");
    209         if($mailType->GroupID)
    210             $group = DataObject::get_one("Group", "ID = $mailType->GroupID");
    211
    212         $recipientField = new RecipientImportField("ImportFile","Import from file", $group );
    213         $recipientField->setTypeID( $id );
    214
    215         // if the file is not valid, then return an error
    216         if( empty( $_FILES ) || empty( $_FILES['ImportFile'] ) || $_FILES['ImportFile']['size'] == 0 )
    217             return $this->customise( array( 'ID' => $id, "UploadForm" => $this->controller->UploadForm( $id ), 'ErrorMessage' => 'Please choose a CSV file to import' ) )->renderWith('Newsletter_RecipientImportField');
    218         elseif( !$this->isValidCSV( $_FILES['ImportFile'] ) ) {
    219             /*if( file_exists( $_FILES['ImportFile']['tmp_name'] ) ) unlink( $_FILES['ImportFile']['tmp_name'] );
    220             unset( $_FILES['ImportFile'] );*/
    221             return $this->customise( array( 'ID' => $id, "UploadForm" => $this->controller->UploadForm( $id ), 'ErrorMessage' => 'The selected file was not a CSV file' ) )->renderWith('Newsletter_RecipientImportField');
    222         } else
    223             return $recipientField->displaytable();
    224     }
    225
    226     function isValidCSV( $file ) {
    227         return preg_match( '/.*\.csv$/i', $file['name'] ) > 0;
    228     }
    229
    230     function confirm( $data, $form ) {
    231         $id = $data['ID'];
    232         $mailType = DataObject::get_one("NewsletterType", "ID = $id");
    233         if($mailType->GroupID)
    234             $group = DataObject::get_one("Group", "ID = $mailType->GroupID");
    235         // @TODO Look into seeing if $data['Set'] should be removed since it seems to be undefined
    236         return $this->importMembers( $id, $group, $data['ImportFileColumns'], (isset($data['Set']) ? $data['Set'] : array()));
    237     }
    238
    239     function cancel( $data, $form ) {
    240         $newForm = $this->controller->UploadForm( $data['ID'] );
    241         return $newForm->forTemplate();
    242     }
    243
    244     function action_import( $data, $form ) {
    245         return $this->import( $data, $form );
    246     }
    247
    248     function action_confirm( $data, $form ) {
    249         return $this->confirm( $data, $form );
    250     }
    251
    252     function action_cancel( $data, $form ) {
    253         return $this->cancel( $data, $form );
    254     }
    255
    256     /**
    257      * Import members from the uploaded file
    258      */
    259     protected function importMembers( $id, $group, $cols, $setFields, $primaryColType = 'Email' ) {
    260
    261         $startTime = time();
    262
    263         $importData = Session::get("ImportFile.{$id}");
    264
    265         $validColumns = array_keys( RecipientImportField::$column_types );
    266
    267         $columnMap = array_flip( $cols );
    268
    269         // Debug::show($columnMap);
    270
    271         // locate the primary column's index
    272         $primaryColumn = /*array_search( $primaryColType, $validColumns );*/ $columnMap[$primaryColType];
    273
    274         // changed fields
    275         $changedFields = array();
    276
    277         // intersect the list of valid columns with the column map to find the columns we need
    278         $importColumns = array_intersect( $validColumns, $cols );
    279
    280         // statistics
    281         $newMembers = 0;
    282         $changedMembers = 0;
    283         $skippedMembers = 0;
    284
    285         // the class that the imported members will become
    286         $newMemberClass = Object::getCustomClass( 'Member' );
    287
    288         // for each row, add a new member or overwrite an existing member
    289         foreach( $importData as $newMemberRow ) {
    290
    291             // skip rows with an empty value for the primary column
    292             if( empty( $newMemberRow[$primaryColumn] ) ) {
    293                 $skippedMembers++;
    294                 continue;
    295             }
    296
    297             // remember to check if the user has unsubscribed
    298             $trackChanges = true;
    299
    300             // TODO: Write DataObject::update
    301             $member = $this->findMember( $newMemberRow[$primaryColumn] );
    302
    303             if( !$member ) {
    304                 $newMembers++;
    305                 $trackChanges = false;
    306                 $member = Object::create("Member");
    307             } else {
    308                 // skip this member if the are unsubscribed
    309                 if( $member->Unsubscribed ) {
    310                     $skippedMembers++;
    311                     continue;
    312                 }
    313
    314                 if( $member->class != $newMemberClass )
    315                     $member->setClassName( $newMemberClass );
    316
    317                 $changedMembers++;
    318             }
    319
    320             // add each of the valid columns
    321             foreach( $importColumns as $datum ) {
    322
    323                 // perform any required conversions
    324                 $newValue = trim( $newMemberRow[$columnMap[$datum]] );
    325                 $oldValue = trim( $member->$datum );
    326
    327                 // Debug::message( "$datum@{$columnMap[$datum]}" );
    328
    329                 // track the modifications to the member data
    330                 if( $trackChanges && $newValue != $oldValue && $datum != $primaryColumn ) {
    331                     $changedFields[] = array(
    332                         $newMemberRow[$primaryColumn],
    333                         "$datum:\n$oldValue",
    334                         "$datum:\n$newValue"
    335                     );
    336
    337                     $numChangedFields++;
    338                 }
    339
    340                 $member->$datum = $newValue;
    341             }
    342
    343             // set any set fields
    344             if( $setFields )
    345                 foreach( $setFields as $fieldName => $fieldValue )
    346                     $member->$fieldName = $fieldValue;
    347
    348             // add member to group
    349             $member->write();
    350             $member->Groups()->add( $group->ID );
    351         }
    352
    353         $numChangedFields = count( $changedFields );
    354         $this->notifyChanges( $changedFields );
    355
    356         // TODO Refresh window
    357         $customData = array(
    358             'ID' => $id,
    359             "UploadForm" => $this->controller->UploadForm( $id ),
    360             'ImportMessage' => 'Imported new members',
    361             'NewMembers' => (string)$newMembers,
    362             'ChangedMembers' => (string)$changedMembers,
    363             'ChangedFields' => (string)$numChangedFields,
    364             'SkippedRecords' => (string)$skippedMembers,
    365             'Time' => time() - $startTime
    366         );
    367         return $this->customise( $customData )->renderWith('Newsletter_RecipientImportField');
    368     }
    369
    370     function findMember( $email ) {
    371         $email = addslashes( $email );
    372         if(defined('DB::USE_ANSI_SQL')) {
    373             return DataObject::get_one( 'Member', "\"Email\"='$email'" );
    374         } else {
    375             return DataObject::get_one( 'Member', "`Email`='$email'" );
    376         }
    377     }
    378
    379     function notifyChanges( $changes ) {
    380         $email = new Email( Email::getAdminEmail(), Email::getAdminEmail(), 'Changed Fields' );
    381
    382         $body = "";
    383
    384         foreach( $changes as $change ) {
    385             $body .= "-------------------------------\n";
    386             $body .= implode( ' ', $change ) . "\n";
    387         }
    388
    389         $email->setBody( $body );
    390         $email->send();
    391     }
    392 }
    393 ?>

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -