mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
3873e4ba00
See https://github.com/silverstripe/silverstripe-framework/pull/7037 and https://github.com/silverstripe/silverstripe-framework/issues/6681 Squashed commit of the following: commit8f65e56532
Author: Ingo Schommer <me@chillu.com> Date: Thu Jun 22 22:25:50 2017 +1200 Fixed upgrade guide spelling commit76f95944fa
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 16:38:34 2017 +1200 BUG Fix non-test class manifest including sapphiretest / functionaltest commit9379834cb4
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 15:50:47 2017 +1200 BUG Fix nesting bug in Kernel commit188ce35d82
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 15:14:51 2017 +1200 BUG fix db bootstrapping issues commit7ed4660e7a
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 14:49:07 2017 +1200 BUG Fix issue in DetailedErrorFormatter commit738f50c497
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 11:49:19 2017 +1200 Upgrading notes on mysite/_config.php commit6279d28e5e
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 11:43:28 2017 +1200 Update developer documentation commit5c90d53a84
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 10:48:44 2017 +1200 Update installer to not use global databaseConfig commitf9b2ba4755
Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 21 21:04:39 2017 +1200 Fix behat issues commit5b59a912b6
Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 21 17:07:11 2017 +1200 Move HTTPApplication to SilverStripe\Control namespace commite2c4a18f63
Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 21 16:29:03 2017 +1200 More documentation Fix up remaining tests Refactor temp DB into TempDatabase class so it’s available outside of unit tests. commit5d235e64f3
Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 21 12:13:15 2017 +1200 API HTTPRequestBuilder::createFromEnvironment() now cleans up live globals BUG Fix issue with SSViewer Fix Security / View tests commitd88d4ed4e4
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 20 16:39:43 2017 +1200 API Refactor AppKernel into CoreKernel commitf7946aec33
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 20 16:00:40 2017 +1200 Docs and minor cleanup commit12bd31f936
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 20 15:34:34 2017 +1200 API Remove OutputMiddleware API Move environment / global / ini management into Environment class API Move getTempFolder into TempFolder class API Implement HTTPRequestBuilder / CLIRequestBuilder BUG Restore SS_ALLOWED_HOSTS check in original location API CoreKernel now requires $basePath to be passed in API Refactor installer.php to use application to bootstrap API move memstring conversion globals to Convert BUG Fix error in CoreKernel nesting not un-nesting itself properly. commitbba9791146
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 18:07:53 2017 +1200 API Create HTTPMiddleware and standardise middleware for request handling commit2a10c2397b
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 17:42:42 2017 +1200 Fixed ORM tests commitd75a8d1d93
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 17:15:07 2017 +1200 FIx i18n tests commit06364af3c3
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 16:59:34 2017 +1200 Fix controller namespace Move states to sub namespace commit2a278e2953
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 12:49:45 2017 +1200 Fix forms namespace commitb65c21241b
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 15 18:56:48 2017 +1200 Update API usages commitd1d4375c95
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 15 18:41:44 2017 +1200 API Refactor $flush into HTPPApplication API Enforce health check in Controller::pushCurrent() API Better global backup / restore Updated Director::test() to use new API commitb220534f06
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 22:05:57 2017 +1200 Move app nesting to a test state helper commit603704165c
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 21:46:04 2017 +1200 Restore kernel stack to fix multi-level nesting commit2f6336a15b
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 17:23:21 2017 +1200 API Implement kernel nesting commitfc7188da7d
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 15:43:13 2017 +1200 Fix core tests commita0ae723514
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 15:23:52 2017 +1200 Fix manifest tests commitca03395251
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 15:00:00 2017 +1200 API Move extension management into test state commitc66d433977
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 14:10:59 2017 +1200 API Refactor SapphireTest state management into SapphireTestState API Remove Injector::unregisterAllObjects() API Remove FakeController commitf26ae75c6e
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 12 18:04:34 2017 +1200 Implement basic CLI application object commit001d559662
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 12 17:39:38 2017 +1200 Remove references to SapphireTest::is_running_test() Upgrade various code commitde079c041d
Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 7 18:07:33 2017 +1200 API Implement APP object API Refactor of Session
257 lines
8.1 KiB
PHP
257 lines
8.1 KiB
PHP
<?php
|
|
|
|
namespace SilverStripe\Dev;
|
|
|
|
use SilverStripe\Core\Environment;
|
|
use SilverStripe\ORM\DataObject;
|
|
use SilverStripe\View\ViewableData;
|
|
|
|
/**
|
|
* A base for bulk loaders of content into the SilverStripe database.
|
|
* Bulk loaders give SilverStripe authors the ability to do large-scale uploads into their SilverStripe databases.
|
|
*
|
|
* You can configure column-handling,
|
|
*
|
|
* @todo Add support for adding/editing has_many relations.
|
|
* @todo Add support for deep chaining of relation properties (e.g. Player.Team.Stats.GoalCount)
|
|
* @todo Character conversion
|
|
*
|
|
* @see http://tools.ietf.org/html/rfc4180
|
|
* @author Ingo Schommer, Silverstripe Ltd. (<firstname>@silverstripe.com)
|
|
*/
|
|
abstract class BulkLoader extends ViewableData
|
|
{
|
|
|
|
/**
|
|
* Each row in the imported dataset should map to one instance
|
|
* of this class (with optional property translation
|
|
* through {@self::$columnMaps}.
|
|
*
|
|
* @var string
|
|
*/
|
|
public $objectClass;
|
|
|
|
/**
|
|
* Override this on subclasses to give the specific functions names.
|
|
*
|
|
* @var string
|
|
*/
|
|
public static $title;
|
|
|
|
/**
|
|
* Map columns to DataObject-properties.
|
|
* If not specified, we assume the first row
|
|
* in the file contains the column headers.
|
|
* The order of your array should match the column order.
|
|
*
|
|
* The column count should match the count of array elements,
|
|
* fill with NULL values if you want to skip certain columns.
|
|
*
|
|
* You can also combine {@link $hasHeaderRow} = true and {@link $columnMap}
|
|
* and omit the NULL values in your map.
|
|
*
|
|
* Supports one-level chaining of has_one relations and properties with dot notation
|
|
* (e.g. Team.Title). The first part has to match a has_one relation name
|
|
* (not necessarily the classname of the used relation).
|
|
*
|
|
* <code>
|
|
* <?php
|
|
* // simple example
|
|
* array(
|
|
* 'Title',
|
|
* 'Birthday'
|
|
* )
|
|
*
|
|
* // complex example
|
|
* array(
|
|
* 'first name' => 'FirstName', // custom column name
|
|
* null, // ignored column
|
|
* 'RegionID', // direct has_one/has_many ID setting
|
|
* 'OrganisationTitle', // create has_one relation to existing record using $relationCallbacks
|
|
* 'street' => 'Organisation.StreetName', // match an existing has_one or create one and write property.
|
|
* );
|
|
* ?>
|
|
* </code>
|
|
*
|
|
* @var array
|
|
*/
|
|
public $columnMap = array();
|
|
|
|
/**
|
|
* Find a has_one relation based on a specific column value.
|
|
*
|
|
* <code>
|
|
* <?php
|
|
* array(
|
|
* 'OrganisationTitle' => array(
|
|
* 'relationname' => 'Organisation', // relation accessor name
|
|
* 'callback' => 'getOrganisationByTitle',
|
|
* );
|
|
* );
|
|
* ?>
|
|
* </code>
|
|
*
|
|
* @var array
|
|
*/
|
|
public $relationCallbacks = array();
|
|
|
|
/**
|
|
* Specifies how to determine duplicates based on one or more provided fields
|
|
* in the imported data, matching to properties on the used {@link DataObject} class.
|
|
* Alternatively the array values can contain a callback method (see example for
|
|
* implementation details). The callback method should be defined on the source class.
|
|
*
|
|
* NOTE: If you're trying to get a unique Member record by a particular field that
|
|
* isn't Email, you need to ensure that Member is correctly set to the unique field
|
|
* you want, as it will merge any duplicates during {@link Member::onBeforeWrite()}.
|
|
*
|
|
* {@see Member::$unique_identifier_field}.
|
|
*
|
|
* If multiple checks are specified, the first non-empty field "wins".
|
|
*
|
|
* <code>
|
|
* <?php
|
|
* array(
|
|
* 'customernumber' => 'ID',
|
|
* 'phonenumber' => array(
|
|
* 'callback' => 'getByImportedPhoneNumber'
|
|
* )
|
|
* );
|
|
* ?>
|
|
* </code>
|
|
*
|
|
* @var array
|
|
*/
|
|
public $duplicateChecks = array();
|
|
|
|
/**
|
|
* @var Boolean $clearBeforeImport Delete ALL records before importing.
|
|
*/
|
|
public $deleteExistingRecords = false;
|
|
|
|
public function __construct($objectClass)
|
|
{
|
|
$this->objectClass = $objectClass;
|
|
parent::__construct();
|
|
}
|
|
|
|
/*
|
|
* Load the given file via {@link self::processAll()} and {@link self::processRecord()}.
|
|
* Optionally truncates (clear) the table before it imports.
|
|
*
|
|
* @return BulkLoader_Result See {@link self::processAll()}
|
|
*/
|
|
public function load($filepath)
|
|
{
|
|
Environment::increaseTimeLimitTo(3600);
|
|
Environment::increaseMemoryLimitTo('512M');
|
|
|
|
//get all instances of the to be imported data object
|
|
if ($this->deleteExistingRecords) {
|
|
DataObject::get($this->objectClass)->removeAll();
|
|
}
|
|
|
|
return $this->processAll($filepath);
|
|
}
|
|
|
|
/**
|
|
* Preview a file import (don't write anything to the database).
|
|
* Useful to analyze the input and give the users a chance to influence
|
|
* it through a UI.
|
|
*
|
|
* @param string $filepath Absolute path to the file we're importing
|
|
* @return array See {@link self::processAll()}
|
|
*/
|
|
abstract public function preview($filepath);
|
|
|
|
/**
|
|
* Process every record in the file
|
|
*
|
|
* @param string $filepath Absolute path to the file we're importing (with UTF8 content)
|
|
* @param boolean $preview If true, we'll just output a summary of changes but not actually do anything
|
|
* @return BulkLoader_Result A collection of objects which are either created, updated or deleted.
|
|
* 'message': free-text string that can optionally provide some more information about what changes have
|
|
*/
|
|
abstract protected function processAll($filepath, $preview = false);
|
|
|
|
|
|
/**
|
|
* Process a single record from the file.
|
|
*
|
|
* @param array $record An map of the data, keyed by the header field defined in {@link self::$columnMap}
|
|
* @param array $columnMap
|
|
* @param $result BulkLoader_Result (passed as reference)
|
|
* @param boolean $preview
|
|
*/
|
|
abstract protected function processRecord($record, $columnMap, &$result, $preview = false);
|
|
|
|
/**
|
|
* Return a FieldList containing all the options for this form; this
|
|
* doesn't include the actual upload field itself
|
|
*/
|
|
public function getOptionFields()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Return a human-readable name for this object.
|
|
* It defaults to the class name can be overridden by setting the static variable $title
|
|
*
|
|
* @return string
|
|
*/
|
|
public function Title()
|
|
{
|
|
$title = $this->stat('title');
|
|
return $title ?: static::class;
|
|
}
|
|
|
|
/**
|
|
* Get a specification of all available columns and relations on the used model.
|
|
* Useful for generation of spec documents for technical end users.
|
|
*
|
|
* Return Format:
|
|
* <code>
|
|
* array(
|
|
* 'fields' => array('myFieldName'=>'myDescription'),
|
|
* 'relations' => array('myRelationName'=>'myDescription'),
|
|
* )
|
|
* </code>
|
|
*
|
|
* @todo Mix in custom column mappings
|
|
*
|
|
* @return array
|
|
**/
|
|
public function getImportSpec()
|
|
{
|
|
$spec = array();
|
|
|
|
// get database columns (fieldlabels include fieldname as a key)
|
|
// using $$includerelations flag as false, so that it only contain $db fields
|
|
$spec['fields'] = (array)singleton($this->objectClass)->fieldLabels(false);
|
|
|
|
$has_ones = singleton($this->objectClass)->hasOne();
|
|
$has_manys = singleton($this->objectClass)->hasMany();
|
|
$many_manys = singleton($this->objectClass)->manyMany();
|
|
|
|
$spec['relations'] = (array)$has_ones + (array)$has_manys + (array)$many_manys;
|
|
|
|
return $spec;
|
|
}
|
|
|
|
/**
|
|
* Determines if a specific field is null.
|
|
* Can be useful for unusual "empty" flags in the file,
|
|
* e.g. a "(not set)" value.
|
|
* The usual {@link DBField::isNull()} checks apply when writing the {@link DataObject},
|
|
* so this is mainly a customization method.
|
|
*
|
|
* @param mixed $val
|
|
* @param string $fieldName Name of the field as specified in the array-values for {@link self::$columnMap}.
|
|
* @return boolean
|
|
*/
|
|
protected function isNullValue($val, $fieldName = null)
|
|
{
|
|
return (empty($val) && $val !== '0');
|
|
}
|
|
}
|