Webylon 3.1 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • auth
  • Booking
  • cart
    • shipping
    • steppedcheckout
  • Catalog
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • BulkLoader
  • BulkLoader_Result
  • CsvBulkLoader
  1 <?php
  2 /**
  3  * Uses the fgetcsv() function to process CSV input.
  4  * The input is expected to be UTF8.
  5  * 
  6  * @see http://rfc.net/rfc4180.html
  7  * @package cms
  8  * @subpackage bulkloading
  9  * @author Ingo Schommer, Silverstripe Ltd. (<myfirstname>@silverstripe.com)
 10  * 
 11  * @todo Support for deleting existing records not matched in the import (through relation checks)
 12  */
 13 class CsvBulkLoader extends BulkLoader {
 14     
 15     /**
 16      * Delimiter character (Default: comma).
 17      *
 18      * @var string
 19      */
 20     public $delimiter = ',';
 21     
 22     /**
 23      * Enclosure character (Default: doublequote)
 24      *
 25      * @var string
 26      */
 27     public $enclosure = '"';
 28     
 29     /**
 30      * Identifies if the has a header row.
 31      * @var boolean
 32      */
 33     public $hasHeaderRow = true;
 34     
 35     protected function processAll($filepath, $preview = false) {
 36         $results = new BulkLoader_Result();
 37         
 38         $csv = new CSVParser($filepath, $this->delimiter, $this->enclosure);
 39         
 40         // ColumnMap has two uses, depending on whether hasHeaderRow is set
 41         if($this->columnMap) {
 42             if($this->hasHeaderRow) $csv->mapColumns($this->columnMap);
 43             else $csv->provideHeaderRow($this->columnMap);
 44         }
 45         
 46         foreach($csv as $row) {
 47             $this->processRecord($row, $this->columnMap, $results, $preview);
 48         }
 49         
 50         return $results;
 51     }
 52     
 53     /**
 54      * @todo Better messages for relation checks and duplicate detection
 55      * Note that columnMap isn't used
 56      */
 57     protected function processRecord($record, $columnMap, &$results, $preview = false) {
 58         $class = $this->objectClass;
 59         
 60         // find existing object, or create new one
 61         $existingObj = $this->findExistingObject($record, $columnMap);
 62         $obj = ($existingObj) ? $existingObj : new $class(); 
 63         
 64         // first run: find/create any relations and store them on the object
 65         // we can't combine runs, as other columns might rely on the relation being present
 66         $relations = array();
 67         foreach($record as $fieldName => $val) {
 68             // don't bother querying of value is not set
 69             if($this->isNullValue($val)) continue;
 70             
 71             // checking for existing relations
 72             if(isset($this->relationCallbacks[$fieldName])) {
 73                 // trigger custom search method for finding a relation based on the given value
 74                 // and write it back to the relation (or create a new object)
 75                 $relationName = $this->relationCallbacks[$fieldName]['relationname'];
 76                 if($this->hasMethod($this->relationCallbacks[$fieldName]['callback'])) {
 77                     $relationObj = $this->{$this->relationCallbacks[$fieldName]['callback']}($obj, $val, $record);
 78                 } elseif($obj->hasMethod($this->relationCallbacks[$fieldName]['callback'])) {
 79                     $relationObj = $obj->{$this->relationCallbacks[$fieldName]['callback']}($val, $record);
 80                 }
 81                 if(!$relationObj || !$relationObj->exists()) {
 82                     $relationClass = $obj->has_one($relationName);
 83                     $relationObj = new $relationClass();
 84                     $relationObj->write();
 85                 }
 86                 $obj->setComponent($relationName, $relationObj);
 87                 $obj->{"{$relationName}ID"} = $relationObj->ID;
 88                 $obj->write();
 89                 $obj->flushCache(); // avoid relation caching confusion
 90                 
 91             } elseif(strpos($fieldName, '.') !== false) {
 92                 // we have a relation column with dot notation
 93                 list($relationName,$columnName) = explode('.', $fieldName);
 94                 $relationObj = $obj->getComponent($relationName); // always gives us an component (either empty or existing)
 95                 $obj->setComponent($relationName, $relationObj);
 96                 $relationObj->write();
 97                 $obj->{"{$relationName}ID"} = $relationObj->ID;
 98                 $obj->write();
 99                 $obj->flushCache(); // avoid relation caching confusion
100             }
101             
102         }
103 
104         // second run: save data
105         foreach($record as $fieldName => $val) {
106             if($this->isNullValue($val, $fieldName)) continue;
107             if(strpos($fieldName, '->') !== FALSE) {
108                 $funcName = substr($fieldName, 2);
109                 $this->$funcName($obj, $val, $record);
110             } else if($obj->hasMethod("import{$fieldName}")) {
111                 $obj->{"import{$fieldName}"}($val, $record);
112             } else {
113                 $obj->update(array($fieldName => $val));
114             }
115         }
116 
117         // write record
118         $id = ($preview) ? 0 : $obj->write();
119         
120         // @todo better message support
121         $message = '';
122         
123         // save to results
124         if($existingObj) {
125             $results->addUpdated($obj, $message);
126         } else {
127             $results->addCreated($obj, $message);
128         }
129         
130         $objID = $obj->ID;
131         
132         $obj->destroy();
133         
134         // memory usage
135         unset($existingObj);
136         unset($obj);
137         
138         return $objID;
139     }
140     
141     /**
142      * Find an existing objects based on one or more uniqueness
143      * columns specified via {@link self::$duplicateChecks}
144      *
145      * @param array $record CSV data column
146      * @return unknown
147      */
148     public function findExistingObject($record) {
149         $SNG_objectClass = singleton($this->objectClass);
150         
151         // checking for existing records (only if not already found)
152         foreach($this->duplicateChecks as $fieldName => $duplicateCheck) {
153             if(is_string($duplicateCheck)) {
154                 $SQL_fieldName = Convert::raw2sql($duplicateCheck); 
155                 if(!isset($record[$fieldName])) {
156                     return false;
157                     //user_error("CsvBulkLoader:processRecord: Couldn't find duplicate identifier '{$fieldName}' in columns", E_USER_ERROR);
158                 }
159                 $SQL_fieldValue = $record[$fieldName];
160                 $existingRecord = DataObject::get_one($this->objectClass, "\"$SQL_fieldName\" = '{$SQL_fieldValue}'");
161                 if($existingRecord) return $existingRecord;
162             } elseif(is_array($duplicateCheck) && isset($duplicateCheck['callback'])) {
163                 if($this->hasMethod($duplicateCheck['callback'])) {
164                     $existingRecord = $this->{$duplicateCheck['callback']}($record[$fieldName], $record);
165                 } elseif($SNG_objectClass->hasMethod($duplicateCheck['callback'])) {
166                     $existingRecord = $SNG_objectClass->{$duplicateCheck['callback']}($record[$fieldName], $record);
167                 } else {
168                     user_error("CsvBulkLoader::processRecord(): {$duplicateCheck['callback']} not found on importer or object class.", E_USER_ERROR);
169                 }
170                 
171                 if($existingRecord) return $existingRecord;
172             } else {
173                 user_error('CsvBulkLoader::processRecord(): Wrong format for $duplicateChecks', E_USER_ERROR);
174             }
175         }
176         
177         return false;
178     }
179     
180     
181     /**
182      * Determine wether any loaded files should be parsed
183      * with a header-row (otherwise we rely on {@link self::$columnMap}.
184      *
185      * @return boolean
186      */
187     public function hasHeaderRow() {
188         return ($this->hasHeaderRow || isset($this->columnMap));
189     }
190     
191 }
192 ?>
[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. -
Webylon 3.1 API Docs API documentation generated by ApiGen 2.8.0