/**
  * @uses ModelAsController::getNestedController()
  * @param SS_HTTPRequest $request
  * @param DataModel $model
  * @return SS_HTTPResponse
  */
 public function handleRequest(SS_HTTPRequest $request, DataModel $model)
 {
     $this->setRequest($request);
     $this->setDataModel($model);
     $this->pushCurrent();
     // Create a response just in case init() decides to redirect
     $this->response = new SS_HTTPResponse();
     $this->init();
     // If we had a redirection or something, halt processing.
     if ($this->response->isFinished()) {
         $this->popCurrent();
         return $this->response;
     }
     // If the database has not yet been created, redirect to the build page.
     if (!DB::is_active() || !ClassInfo::hasTable('SiteTree')) {
         $this->response->redirect(Director::absoluteBaseURL() . 'dev/build?returnURL=' . (isset($_GET['url']) ? urlencode($_GET['url']) : null));
         $this->popCurrent();
         return $this->response;
     }
     try {
         $result = $this->getNestedController();
         if ($result instanceof RequestHandler) {
             $result = $result->handleRequest($this->getRequest(), $model);
         } else {
             if (!$result instanceof SS_HTTPResponse) {
                 user_error("ModelAsController::getNestedController() returned bad object type '" . get_class($result) . "'", E_USER_WARNING);
             }
         }
     } catch (SS_HTTPResponse_Exception $responseException) {
         $result = $responseException->getResponse();
     }
     $this->popCurrent();
     return $result;
 }
 private static function truncate_table($table)
 {
     if (ClassInfo::hasTable($table)) {
         if (method_exists(DB::getConn(), 'clearTable')) {
             DB::getConn()->clearTable($table);
         } else {
             DB::query("TRUNCATE \"{$table}\"");
         }
     }
 }
 public function run($request)
 {
     HTTP::set_cache_age(0);
     increase_time_limit_to();
     // This can be a time consuming task
     $conn = DB::getConn();
     $classes = ClassInfo::subclassesFor('DataObject');
     $dbTables = $conn->tableList();
     $go = $request->getVar('go');
     if (!$go) {
         echo 'Set ?go=1 to really delete the tables';
         echo '<hr/>';
     }
     //make all lowercase
     $dbTablesLc = array_map('strtolower', $dbTables);
     $dbTablesMap = array();
     foreach ($dbTables as $k => $v) {
         $dbTablesMap[strtolower($v)] = $v;
     }
     foreach ($classes as $class) {
         if (ClassInfo::hasTable($class)) {
             $lcClass = strtolower($class);
             self::removeFromArray($lcClass, $dbTablesLc);
             //page modules
             self::removeFromArray($lcClass . '_live', $dbTablesLc);
             self::removeFromArray($lcClass . '_versions', $dbTablesLc);
             //relations
             $hasMany = Config::inst()->get($class, 'has_many');
             $manyMany = Config::inst()->get($class, 'many_many');
             if (!empty($hasMany)) {
                 foreach ($hasMany as $rel => $obj) {
                     self::removeFromArray($lcClass . '_' . strtolower($rel), $dbTablesLc);
                 }
             }
             if (!empty($manyMany)) {
                 foreach ($manyMany as $rel => $obj) {
                     self::removeFromArray($lcClass . '_' . strtolower($rel), $dbTablesLc);
                 }
             }
         }
     }
     //at this point, we should only have orphans table in dbTables var
     foreach ($dbTablesLc as $i => $lcTable) {
         $table = $dbTablesMap[$lcTable];
         if ($go) {
             DB::query('DROP TABLE `' . $table . '`');
             DB::alteration_message("Dropped {$table}", 'obsolete');
         } else {
             DB::alteration_message("Would drop {$table}", 'obsolete');
         }
     }
 }
 public function handleRequest($request)
 {
     self::$is_at_root = true;
     $this->pushCurrent();
     $this->init();
     // If the basic database hasn't been created, then build it.
     if (!DB::isActive() || !ClassInfo::hasTable('SiteTree')) {
         $this->response = new HTTPResponse();
         $this->redirect("dev/build?returnURL=");
         return $this->response;
     }
     $controller = new ModelAsController();
     $request = new HTTPRequest("GET", self::get_homepage_urlsegment() . '/', $request->getVars(), $request->postVars());
     $request->match('$URLSegment//$Action', true);
     $result = $controller->handleRequest($request);
     $this->popCurrent();
     return $result;
 }
 /**
  * Check catalogue URL's before we get to the CMS (if it exists)
  * 
  * @param SS_HTTPRequest $request
  * @param DataModel|null $model
  * @return SS_HTTPResponse
  */
 public function handleRequest(SS_HTTPRequest $request, DataModel $model)
 {
     $this->request = $request;
     $this->setDataModel($model);
     $catalogue_enabled = Catalogue::config()->enable_frontend;
     $this->pushCurrent();
     // Create a response just in case init() decides to redirect
     $this->response = new SS_HTTPResponse();
     $this->init();
     // If we had a redirection or something, halt processing.
     if ($this->response->isFinished()) {
         $this->popCurrent();
         return $this->response;
     }
     // If DB is not present, build
     if (!DB::isActive() || !ClassInfo::hasTable('CatalogueProduct') || !ClassInfo::hasTable('CatalogueCategory')) {
         return $this->response->redirect(Director::absoluteBaseURL() . 'dev/build?returnURL=' . (isset($_GET['url']) ? urlencode($_GET['url']) : null));
     }
     $urlsegment = $request->param('URLSegment');
     $this->extend('onBeforeInit');
     $this->init();
     $this->extend('onAfterInit');
     // Find link, regardless of current locale settings
     if (class_exists('Translatable')) {
         Translatable::disable_locale_filter();
     }
     $filter = array('URLSegment' => $urlsegment, 'Disabled' => 0);
     if ($catalogue_enabled && ($object = CatalogueProduct::get()->filter($filter)->first())) {
         $controller = $this->controller_for($object);
     } elseif ($catalogue_enabled && ($object = CatalogueCategory::get()->filter($filter)->first())) {
         $controller = $this->controller_for($object);
     } elseif (class_exists('ModelAsController')) {
         // If CMS installed
         $controller = ModelAsController::create();
     } else {
         $controller = Controller::create();
     }
     if (class_exists('Translatable')) {
         Translatable::enable_locale_filter();
     }
     $result = $controller->handleRequest($request, $model);
     $this->popCurrent();
     return $result;
 }
 public function handleRequest($request)
 {
     $this->pushCurrent();
     $this->urlParams = $request->allParams();
     $this->init();
     // If the basic database hasn't been created, then build it.
     if (!DB::isActive() || !ClassInfo::hasTable('SiteTree')) {
         $this->response = new HTTPResponse();
         $this->redirect("dev/build?returnURL=" . (isset($_GET['url']) ? urlencode($_GET['url']) : ''));
         $this->popCurrent();
         return $this->response;
     }
     $result = $this->getNestedController();
     if (is_object($result) && $result instanceof RequestHandler) {
         $result = $result->handleRequest($request);
     }
     $this->popCurrent();
     return $result;
 }
 public function run($request)
 {
     $classes = ClassInfo::subclassesFor('DataObject');
     $dbTables = DB::query(DB::getConn()->allTablesSQL())->column();
     $go = $request->getVar('go');
     if (!$go) {
         DB::alteration_message('Set ?go=1 to really delete the tables');
     }
     //make all lowercase
     $dbTablesLc = array_map('strtolower', $dbTables);
     foreach ($classes as $class) {
         if (ClassInfo::hasTable($class)) {
             $lcClass = strtolower($class);
             self::removeFromArray($lcClass, $dbTablesLc);
             //page modules
             self::removeFromArray($lcClass . '_live', $dbTablesLc);
             self::removeFromArray($lcClass . '_versions', $dbTablesLc);
             //relations
             $hasMany = Config::inst()->get($class, 'has_many');
             $manyMany = Config::inst()->get($class, 'many_many');
             if (!empty($hasMany)) {
                 foreach ($hasMany as $rel => $obj) {
                     self::removeFromArray($lcClass . '_' . strtolower($rel), $dbTablesLc);
                 }
             }
             if (!empty($manyMany)) {
                 foreach ($manyMany as $rel => $obj) {
                     self::removeFromArray($lcClass . '_' . strtolower($rel), $dbTablesLc);
                 }
             }
         }
     }
     //at this point, we should only have orphans table in dbTables var
     foreach ($dbTablesLc as $i => $table) {
         if ($go) {
             DB::query('DROP TABLE `' . $table . '`');
             DB::alteration_message("Dropped {$table}", 'obsolete');
         } else {
             DB::alteration_message("Would drop {$table}", 'obsolete');
         }
     }
 }
 public function run($request)
 {
     HTTP::set_cache_age(0);
     increase_time_limit_to();
     // This can be a time consuming task
     $classes = ClassInfo::dataClassesFor('DataObject');
     $conn = DB::getConn();
     $go = $request->getVar('go');
     if (!$go) {
         echo 'Set ?go=1 to really delete the fields';
         echo '<hr/>';
     }
     foreach ($classes as $class) {
         $hasTable = ClassInfo::hasTable($class);
         if (!$hasTable) {
             continue;
         }
         $toDrop = array();
         $fields = $class::database_fields($class);
         $list = $conn->fieldList($class);
         foreach ($list as $fieldName => $type) {
             if ($fieldName == 'ID') {
                 continue;
             }
             if (!isset($fields[$fieldName])) {
                 $toDrop[] = $fieldName;
             }
         }
         if (empty($toDrop)) {
             continue;
         }
         if ($go) {
             $this->dropColumns($class, $toDrop);
             DB::alteration_message("Dropped " . implode(',', $toDrop) . " for {$class}", "obsolete");
         } else {
             DB::alteration_message("Would drop " . implode(',', $toDrop) . " for {$class}", "obsolete");
         }
     }
 }
 public function _getAnnouncement($render = true)
 {
     if (ClassInfo::hasTable('MediaHolder')) {
         $media = MediaHolder::get()->filter(array('MediaType.Title' => 'News'));
         $possible = $media->first();
         // what about an announcement title'd page
         $page = $media->filter(array('Title' => 'Announcements'))->first();
         if (!$page) {
             $page = $possible;
         }
         if ($page) {
             $announcement = MediaPage::get()->filter('ParentID', $page->ID)->sort('Date DESC')->first();
             if ($announcement) {
                 if (!$render) {
                     return $announcement;
                 } else {
                     return ModelAsController::controller_for($announcement)->index();
                 }
             }
         }
     }
 }
 function run($request)
 {
     //TODO: include decendant clases..incase some subclassing has been done somewhere
     if ($allorders = DataObject::get('Order')) {
         foreach ($allorders as $order) {
             $order->delete();
             $order->destroy();
         }
     }
     if ($allproducts = DataObject::get('Product')) {
         foreach ($allproducts as $product) {
             $product->deleteFromStage('Live');
             $product->deleteFromStage('Stage');
             $product->destroy();
             //TODO: remove versions
         }
     }
     //TODO: use TRUNCATE instead?
     $basetables = array('Product', 'Product_Live', 'Product_versions', 'Product_ProductGroups', 'Product_OrderItem', 'Product_VariationAttributes', 'ProductVariation', 'ProductVariation_AttributeValues', 'ProductVariation_OrderItem', 'ProductVariation_versions', 'ProductAttributeType', 'ProductAttributeValue');
     foreach ($basetables as $table) {
         if (!ClassInfo::hasTable($table)) {
             continue;
         }
         foreach (ClassInfo::subclassesFor($table) as $key => $class) {
             if (ClassInfo::hasTable($class)) {
                 DB::query("DELETE FROM \"{$class}\" WHERE 1;");
                 echo "<p>Deleting all {$class}</p>";
             }
         }
     }
     //partial empty queries
     echo "<p>Deleting all SiteTree</p>";
     DB::query("DELETE FROM \"SiteTree\" WHERE ClassName = 'Product';");
     //SiteTree
     DB::query("DELETE FROM \"SiteTree_Live\" WHERE ClassName = 'Product';");
     //SiteTree
     DB::query("DELETE FROM \"SiteTree_versions\" WHERE ClassName = 'Product';");
     //SiteTree
 }
 public function handleRequest(SS_HTTPRequest $request, DataModel $model = null)
 {
     self::$is_at_root = true;
     $this->setDataModel($model);
     $this->pushCurrent();
     $this->init();
     $this->setRequest($request);
     // Check for existing routing parameters, redirecting to another locale automatically if necessary
     $locale = Fluent::get_request_locale();
     if (empty($locale)) {
         // Determine if this user should be redirected
         $locale = $this->getRedirectLocale();
         $this->extend('updateRedirectLocale', $locale);
         // Check if the user should be redirected
         $domainDefault = Fluent::default_locale(true);
         if (Fluent::is_locale($locale) && $locale !== $domainDefault) {
             // Check new traffic with detected locale
             return $this->redirect(Fluent::locale_baseurl($locale));
         }
         // Reset parameters to act in the default locale
         $locale = $domainDefault;
         Fluent::set_persist_locale($locale);
         $params = $request->routeParams();
         $params[Fluent::config()->query_param] = $locale;
         $request->setRouteParams($params);
     }
     if (!DB::isActive() || !ClassInfo::hasTable('SiteTree')) {
         $this->response = new SS_HTTPResponse();
         $this->response->redirect(Director::absoluteBaseURL() . 'dev/build?returnURL=' . (isset($_GET['url']) ? urlencode($_GET['url']) : null));
         return $this->response;
     }
     $localeURL = Fluent::alias($locale);
     $request->setUrl(self::fluent_homepage_link($localeURL));
     $request->match($localeURL . '/$URLSegment//$Action', true);
     $controller = new ModelAsController();
     $result = $controller->handleRequest($request, $model);
     $this->popCurrent();
     return $result;
 }
 function run($request)
 {
     if ($allorders = DataObject::get('Order')) {
         foreach ($allorders as $order) {
             //TODO: delete member(s)?
             $order->delete();
             $order->destroy();
         }
     }
     $basetables = array('Order', 'OrderAttribute', 'OrderStatusLog', 'Payment');
     foreach ($basetables as $table) {
         if (!ClassInfo::hasTable($table)) {
             continue;
         }
         foreach (ClassInfo::subclassesFor($table) as $key => $class) {
             //TODO: empty all pivot(many_many) tables on this side of relationship
             if (ClassInfo::hasTable($class)) {
                 DB::query("DELETE FROM \"{$class}\" WHERE 1;");
                 echo "<p>Deleting all {$class}</p>";
             }
         }
     }
 }
 public function run($request)
 {
     increase_time_limit_to();
     $readingMode = Versioned::get_reading_mode();
     Versioned::reading_stage('Stage');
     // Make sure that we have something to migrate.
     if (!ClassInfo::hasTable('SolrSearchPage')) {
         echo "Nothing to Migrate!";
         die;
     }
     // Retrieve the search tree relationships to migrate.
     $relationships = array();
     if (DB::getConn()->hasTable('SolrSearchPage_SearchTrees')) {
         foreach (DB::query('SELECT * FROM SolrSearchPage_SearchTrees') as $relationship) {
             $relationships[$relationship['SolrSearchPageID']] = $relationship['PageID'];
         }
     }
     // Store the current live page migration state to avoid duplicates.
     $created = array();
     // Migrate any live pages to begin with.
     $query = DB::query('SELECT * FROM SiteTree_Live st, SolrSearchPage_Live ssp WHERE st.ID = ssp.ID');
     $queryCount = $query->numRecords();
     $writeCount = 0;
     foreach ($query as $results) {
         $searchPage = ExtensibleSearchPage::create();
         $searchPage->SearchEngine = 'SolrSearch';
         // Migrate the key site tree and solr search fields across.
         $fields = array('ParentID', 'URLSegment', 'Title', 'MenuTitle', 'Content', 'ShowInMenus', 'ShowInSearch', 'Sort', 'ResultsPerPage', 'SortBy', 'BoostFieldsValue', 'SearchOnFieldsValue', 'SearchTypeValue', 'StartWithListing', 'QueryType', 'ListingTemplateID', 'FilterFieldsValue', 'MinFacetCount', 'FacetQueriesValue', 'FacetMappingValue', 'CustomFacetFieldsValue', 'FacetFieldsValue', 'BoostMatchFieldsValue');
         foreach ($fields as $fname) {
             if (isset($results[$fname])) {
                 $searchPage->{$fname} = $results[$fname];
             }
         }
         // This field name no longer matches the original.
         if ($results['SortDir']) {
             $searchPage->SortDirection = $results['SortDir'];
         }
         if (isset($relationships[$results['ID']])) {
             $searchPage->SearchTrees()->add($relationships[$results['ID']]);
         }
         // Attempt to publish these new pages.
         $searchPage->doPublish();
         if ($searchPage->ID) {
             echo "<strong>{$results['ID']}</strong> Published<br>";
             $writeCount++;
         }
         $created[] = $results['ID'];
     }
     // Confirm that the current user had permission to publish these new pages.
     $this->checkPermissions($queryCount, $writeCount);
     // Migrate any remaining draft pages.
     $query = DB::query('SELECT * FROM SiteTree st, SolrSearchPage ssp WHERE st.ID = ssp.ID');
     $queryCount = $query->numRecords();
     $writeCount = 0;
     foreach ($query as $results) {
         // Make sure this search page doesn't already exist.
         if (!in_array($results['ID'], $created)) {
             $searchPage = ExtensibleSearchPage::create();
             $searchPage->SearchEngine = 'SolrSearch';
             // Migrate the key site tree and solr search fields across.
             $searchPage->ParentID = $results['ParentID'];
             $searchPage->URLSegment = $results['URLSegment'];
             $searchPage->Title = $results['Title'];
             $searchPage->MenuTitle = $results['MenuTitle'];
             $searchPage->Content = $results['Content'];
             $searchPage->ShowInMenus = $results['ShowInMenus'];
             $searchPage->ShowInSearch = $results['ShowInSearch'];
             $searchPage->Sort = $results['Sort'];
             $searchPage->ResultsPerPage = $results['ResultsPerPage'];
             $searchPage->SortBy = $results['SortBy'];
             $searchPage->SortDirection = $results['SortDir'];
             $searchPage->QueryType = $results['QueryType'];
             $searchPage->StartWithListing = $results['StartWithListing'];
             $searchPage->SearchTypeValue = $results['SearchTypeValue'];
             $searchPage->SearchOnFieldsValue = $results['SearchOnFieldsValue'];
             $searchPage->BoostFieldsValue = $results['BoostFieldsValue'];
             $searchPage->BoostMatchFieldsValue = $results['BoostMatchFieldsValue'];
             $searchPage->FacetFieldsValue = $results['FacetFieldsValue'];
             $searchPage->CustomFacetFieldsValue = $results['CustomFacetFieldsValue'];
             $searchPage->FacetMappingValue = $results['FacetMappingValue'];
             $searchPage->FacetQueriesValue = $results['FacetQueriesValue'];
             $searchPage->MinFacetCount = $results['MinFacetCount'];
             $searchPage->FilterFieldsValue = $results['FilterFieldsValue'];
             $searchPage->ListingTemplateID = $results['ListingTemplateID'];
             if (isset($relationships[$results['ID']])) {
                 $searchPage->SearchTrees()->add($relationships[$results['ID']]);
             }
             $searchPage->write();
             if ($searchPage->ID) {
                 echo "<strong>{$results['ID']}</strong> Saved<br>";
                 $writeCount++;
             }
         } else {
             $writeCount++;
         }
     }
     // Confirm that the current user had permission to write these new pages.
     $this->checkPermissions($queryCount, $writeCount);
     // Remove the previous search page tables, as they are now obsolete (and may not be marked as such).
     $remove = array('SiteTree', 'SiteTree_Live', 'Page', 'Page_Live');
     foreach ($remove as $table) {
         foreach ($created as $ID) {
             DB::query("DELETE FROM {$table} WHERE ID = {$ID}");
         }
     }
     $remove = array('SolrSearchPage', 'SolrSearchPage_Live', 'SolrSearchPage_SearchTrees', 'SolrSearchPage_versions');
     foreach ($remove as $table) {
         DB::query("DROP TABLE {$table}");
     }
     Versioned::set_reading_mode($readingMode);
     echo 'Migration Complete!';
 }
Example #14
0
 /**
  * Checks the database is in a state to perform security checks.
  * See {@link DatabaseAdmin->init()} for more information.
  * 
  * @return bool
  */
 public static function database_is_ready()
 {
     // Used for unit tests
     if (self::$force_database_is_ready !== NULL) {
         return self::$force_database_is_ready;
     }
     $requiredTables = ClassInfo::dataClassesFor('Member');
     $requiredTables[] = 'Group';
     $requiredTables[] = 'Permission';
     foreach ($requiredTables as $table) {
         // if any of the tables aren't created in the database
         if (!ClassInfo::hasTable($table)) {
             return false;
         }
         // if any of the tables don't have all fields mapped as table columns
         $dbFields = DB::fieldList($table);
         if (!$dbFields) {
             return false;
         }
         $objFields = DataObject::database_fields($table);
         $missingFields = array_diff_key($objFields, $dbFields);
         if ($missingFields) {
             return false;
         }
     }
     return true;
 }
Example #15
0
 public function getDateJoin()
 {
     $join = "LEFT JOIN `CalendarDateTime` ON `CalendarDateTime`.EventID = `CalendarEvent`.ID";
     if (is_subclass_of($this->getEventDateTimeObject(), "CalendarDateTime")) {
         $parents = array_reverse(ClassInfo::ancestry($this->getEventDateTimeClass()));
         foreach ($parents as $class) {
             if (ClassInfo::hasTable($class)) {
                 if ($class == "CalendarDateTime") {
                     break;
                 }
                 $join .= " LEFT JOIN `{$class}` ON `{$class}`.ID = `CalendarDateTime`.ID";
             }
         }
     }
     return $join;
 }
 /**
  * Determine if the DB is ready to use.
  *
  * @return bool
  * @throws Exception
  */
 protected function isDatabaseReady()
 {
     // Such as during setup of testsession prior to DB connection.
     if (!DB::isActive()) {
         return false;
     }
     // If we have a DB of the wrong type then complain
     if (!DB::getConn() instanceof MySQLDatabase) {
         throw new Exception('HybridSessionStore currently only works with MySQL databases');
     }
     // Prevent freakout during dev/build
     return ClassInfo::hasTable('HybridSessionDataObject');
 }
Example #17
0
	/**
	 * Checks the database is in a state to perform security checks.
	 * @return bool
	 */
	public static function database_is_ready() {
		$requiredTables = ClassInfo::dataClassesFor('Member');
		$requiredTables[] = 'Group';
		$requiredTables[] = 'Permission';
		
		foreach($requiredTables as $table) if(!ClassInfo::hasTable($table)) return false;
		
		return (($permissionFields = DB::fieldList('Permission')) && isset($permissionFields['Type'])) &&
			(($memberFields = DB::fieldList('Member')) && isset($memberFields['RememberLoginToken']));
	}
 /**
  * Traverse the relationship fields, and add the table
  * mappings to the query object state. This has to be called
  * in any overloaded {@link SearchFilter->apply()} methods manually.
  * 
  * @param String|array $relation The array/dot-syntax relation to follow
  * @return The model class of the related item
  */
 public function applyRelation($relation)
 {
     // NO-OP
     if (!$relation) {
         return $this->dataClass;
     }
     if (is_string($relation)) {
         $relation = explode(".", $relation);
     }
     $modelClass = $this->dataClass;
     foreach ($relation as $rel) {
         $model = singleton($modelClass);
         if ($component = $model->has_one($rel)) {
             if (!$this->query->isJoinedTo($component)) {
                 $foreignKey = $rel;
                 $realModelClass = ClassInfo::table_for_object_field($modelClass, "{$foreignKey}ID");
                 $this->query->addLeftJoin($component, "\"{$component}\".\"ID\" = \"{$realModelClass}\".\"{$foreignKey}ID\"");
                 /**
                  * add join clause to the component's ancestry classes so that the search filter could search on
                  * its ancestor fields.
                  */
                 $ancestry = ClassInfo::ancestry($component, true);
                 if (!empty($ancestry)) {
                     $ancestry = array_reverse($ancestry);
                     foreach ($ancestry as $ancestor) {
                         if ($ancestor != $component) {
                             $this->query->addInnerJoin($ancestor, "\"{$component}\".\"ID\" = \"{$ancestor}\".\"ID\"");
                         }
                     }
                 }
             }
             $modelClass = $component;
         } elseif ($component = $model->has_many($rel)) {
             if (!$this->query->isJoinedTo($component)) {
                 $ancestry = $model->getClassAncestry();
                 $foreignKey = $model->getRemoteJoinField($rel);
                 $this->query->addLeftJoin($component, "\"{$component}\".\"{$foreignKey}\" = \"{$ancestry[0]}\".\"ID\"");
                 /**
                  * add join clause to the component's ancestry classes so that the search filter could search on
                  * its ancestor fields.
                  */
                 $ancestry = ClassInfo::ancestry($component, true);
                 if (!empty($ancestry)) {
                     $ancestry = array_reverse($ancestry);
                     foreach ($ancestry as $ancestor) {
                         if ($ancestor != $component) {
                             $this->query->addInnerJoin($ancestor, "\"{$component}\".\"ID\" = \"{$ancestor}\".\"ID\"");
                         }
                     }
                 }
             }
             $modelClass = $component;
         } elseif ($component = $model->many_many($rel)) {
             list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component;
             $parentBaseClass = ClassInfo::baseDataClass($parentClass);
             $componentBaseClass = ClassInfo::baseDataClass($componentClass);
             $this->query->addInnerJoin($relationTable, "\"{$relationTable}\".\"{$parentField}\" = \"{$parentBaseClass}\".\"ID\"");
             $this->query->addLeftJoin($componentBaseClass, "\"{$relationTable}\".\"{$componentField}\" = \"{$componentBaseClass}\".\"ID\"");
             if (ClassInfo::hasTable($componentClass)) {
                 $this->query->addLeftJoin($componentClass, "\"{$relationTable}\".\"{$componentField}\" = \"{$componentClass}\".\"ID\"");
             }
             $modelClass = $componentClass;
         }
     }
     return $modelClass;
 }
Example #19
0
 /**
  * Checks the database is in a state to perform security checks.
  * @return bool
  */
 public static function database_is_ready()
 {
     return ClassInfo::hasTable('Member') && ClassInfo::hasTable('Group') && ClassInfo::hasTable('Permission') && (($permissionFields = DB::fieldList('Permission')) && isset($permissionFields['Type'])) && (($memberFields = DB::fieldList('Member')) && isset($memberFields['RememberLoginToken']));
 }
 /**
  *	Return all data object visible attributes of the specified type, with optional filters.
  *
  *	@parameter <{DATA_OBJECT_NAME}> string
  *	@parameter <{LIMIT}> integer
  *	@parameter <{SORT}> array(string, string)
  *	@parameter <{FILTERS}> array
  *	@return array
  */
 public function retrieveValidated($class, $limit = null, $sort = null, $filters = null)
 {
     // Validate the data object class.
     $class = strtolower($class);
     if (in_array($class, array_map('strtolower', ClassInfo::subclassesFor('DataObject'))) && ($configuration = DataObjectOutputConfiguration::get_one('DataObjectOutputConfiguration', "LOWER(IsFor) = '" . Convert::raw2sql($class) . "'")) && ($temporaryClass = DataObject::get_one($class))) {
         $class = ClassInfo::baseDataClass($temporaryClass->ClassName);
         $visibility = $configuration->APIwesomeVisibility ? explode(',', $configuration->APIwesomeVisibility) : null;
         // Validate the sort and filters.
         $where = array();
         $sortValid = is_array($sort) && count($sort) === 2 && ($order = strtoupper($sort[1])) && ($order === 'ASC' || $order === 'DESC');
         $filterValid = is_array($filters) && count($filters);
         $sorting = array();
         $filtering = array();
         // Grab the appropriate attributes for this data object.
         if (is_subclass_of($class, 'SiteTree')) {
             $where[] = "ClassName = '{$class}'";
             $class = 'SiteTree';
         } else {
             if (is_subclass_of($class, 'File')) {
                 $where[] = "ClassName = '{$class}'";
                 $class = 'File';
             }
         }
         $columns = array();
         $from = array();
         foreach (ClassInfo::subclassesFor($class) as $subclass) {
             // Determine the tables to join.
             $subclassFields = DataObject::database_fields($subclass);
             if (ClassInfo::hasTable($subclass)) {
                 // Determine the versioned table.
                 $same = $subclass === $class;
                 if ($subclass::has_extension('Versioned')) {
                     $subclass = "{$subclass}_Live";
                 }
                 if (!$same) {
                     $from[] = $subclass;
                 }
             }
             // Prepend the table names.
             $subclassColumns = array();
             foreach ($subclassFields as $column => $type) {
                 $subclassColumn = "{$subclass}.{$column}";
                 $subclassColumns[$subclassColumn] = $type;
                 // Determine the tables to sort and filter on.
                 if ($sortValid && $sort[0] === $column) {
                     $sorting[] = "{$subclassColumn} {$order}";
                 }
                 if ($filterValid && isset($filters[$column])) {
                     $filtering[$subclassColumn] = is_numeric($filters[$column]) ? "{$subclassColumn} = " . (int) $filters[$column] : "LOWER({$subclassColumn}) = '" . Convert::raw2sql(strtolower($filters[$column])) . "'";
                 }
             }
             $columns = array_merge($columns, $subclassColumns);
         }
         array_shift($columns);
         // Determine the versioned table.
         if ($class::has_extension('Versioned')) {
             $class = "{$class}_Live";
         }
         // Determine ID based sorting and filtering, as these aren't considered database fields.
         if ($sortValid && $sort[0] === 'ID') {
             $sorting[] = "{$class}.ID {$order}";
         }
         if ($filterValid && isset($filters['ID'])) {
             $where[] = "{$class}.ID = " . (int) $filters['ID'];
         }
         // Make sure this data object type has visibility customisation.
         if ($visibility && count($visibility) === count($columns) && in_array('1', $visibility)) {
             // Apply any visibility customisation.
             $select = ' ';
             $iteration = 0;
             foreach ($columns as $attribute => $type) {
                 if (isset($visibility[$iteration]) && $visibility[$iteration]) {
                     $select .= $attribute . ', ';
                     if (isset($filtering[$attribute])) {
                         // Apply the filter if the matching attribute is visible.
                         $where[] = $filtering[$attribute];
                     }
                 }
                 $iteration++;
             }
             if (isset($filtering["{$class}.ClassName"])) {
                 $where[] = $filtering["{$class}.ClassName"];
             }
             // Grab all data object visible attributes.
             $query = new SQLQuery("{$class}.ClassName,{$select}{$class}.ID", $class, $where, $sorting, array(), array(), is_numeric($limit) ? $limit : array());
             // Determine the tables with visible attributes to join.
             foreach ($from as $join) {
                 if (strpos($select, " {$join}.") !== false) {
                     $query->addLeftJoin($join, "{$class}.ID = {$join}.ID");
                 }
             }
             $objects = array();
             foreach ($query->execute() as $temporary) {
                 // Return an array of data object maps.
                 $object = array();
                 foreach ($temporary as $attribute => $value) {
                     if ($value) {
                         $object[$attribute] = $value;
                     }
                 }
                 $objects[] = $object;
             }
             return $objects;
         }
     }
     // The specified data object type had no visibility customisation.
     return null;
 }
 private static function truncate_table($table)
 {
     DB::alteration_message("Truncating Table {$table}", "deleted");
     if (ClassInfo::hasTable($table)) {
         if (method_exists(DB::getConn(), 'clearTable')) {
             DB::getConn()->clearTable($table);
         } else {
             DB::query("TRUNCATE \"{$table}\"");
         }
     }
 }
 /**
  * Traverse the relationship fields, and add the table
  * mappings to the query object state. This has to be called
  * in any overloaded {@link SearchFilter->apply()} methods manually.
  * 
  * @todo try to make this implicitly triggered so it doesn't have to be manually called in child filters
  * @param SQLQuery $query
  * @return SQLQuery
  */
 function applyRelation($query)
 {
     if (is_array($this->relation)) {
         foreach ($this->relation as $rel) {
             $model = singleton($this->model);
             if ($component = $model->has_one($rel)) {
                 if (!$query->isJoinedTo($component)) {
                     $foreignKey = $model->getReverseAssociation($component);
                     $query->leftJoin($component, "\"{$component}\".\"ID\" = \"{$this->model}\".\"{$foreignKey}ID\"");
                     /**
                      * add join clause to the component's ancestry classes so that the search filter could search on its 
                      * ancester fields.
                      */
                     $ancestry = ClassInfo::ancestry($component, true);
                     if (!empty($ancestry)) {
                         $ancestry = array_reverse($ancestry);
                         foreach ($ancestry as $ancestor) {
                             if ($ancestor != $component) {
                                 $query->innerJoin($ancestor, "\"{$component}\".\"ID\" = \"{$ancestor}\".\"ID\"");
                                 $component = $ancestor;
                             }
                         }
                     }
                 }
                 $this->model = $component;
             } elseif ($component = $model->has_many($rel)) {
                 if (!$query->isJoinedTo($component)) {
                     $ancestry = $model->getClassAncestry();
                     $foreignKey = $model->getRemoteJoinField($rel);
                     $query->leftJoin($component, "\"{$component}\".\"{$foreignKey}\" = \"{$ancestry[0]}\".\"ID\"");
                     /**
                      * add join clause to the component's ancestry classes so that the search filter could search on its 
                      * ancestor fields.
                      */
                     $ancestry = ClassInfo::ancestry($component, true);
                     if (!empty($ancestry)) {
                         $ancestry = array_reverse($ancestry);
                         foreach ($ancestry as $ancestor) {
                             if ($ancestor != $component) {
                                 $query->innerJoin($ancestor, "\"{$component}\".\"ID\" = \"{$ancestor}\".\"ID\"");
                                 $component = $ancestor;
                             }
                         }
                     }
                 }
                 $this->model = $component;
             } elseif ($component = $model->many_many($rel)) {
                 list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component;
                 $parentBaseClass = ClassInfo::baseDataClass($parentClass);
                 $componentBaseClass = ClassInfo::baseDataClass($componentClass);
                 $query->innerJoin($relationTable, "\"{$relationTable}\".\"{$parentField}\" = \"{$parentBaseClass}\".\"ID\"");
                 $query->leftJoin($componentBaseClass, "\"{$relationTable}\".\"{$componentField}\" = \"{$componentBaseClass}\".\"ID\"");
                 if (ClassInfo::hasTable($componentClass)) {
                     $query->leftJoin($componentClass, "\"{$relationTable}\".\"{$componentField}\" = \"{$componentClass}\".\"ID\"");
                 }
                 $this->model = $componentClass;
                 // Experimental support for user-defined relationships via a "(relName)Query" method
                 // This will likely be dropped in 2.4 for a system that makes use of Lazy Data Lists.
             } elseif ($model->hasMethod($rel . 'Query')) {
                 // Get the query representing the join - it should have "$ID" in the filter
                 $newQuery = $model->{"{$rel}Query"}();
                 if ($newQuery) {
                     // Get the table to join to
                     //DATABASE ABSTRACTION: I don't think we need this line anymore:
                     $newModel = str_replace('`', '', array_shift($newQuery->from));
                     // Get the filter to use on the join
                     $ancestry = $model->getClassAncestry();
                     $newFilter = "(" . str_replace('$ID', "\"{$ancestry[0]}\".\"ID\"", implode(") AND (", $newQuery->where)) . ")";
                     $query->leftJoin($newModel, $newFilter);
                     $this->model = $newModel;
                 } else {
                     $this->name = "NULL";
                     return;
                 }
             }
         }
     }
     return $query;
 }
 /**
  * Checks the database is in a state to perform security checks.
  * See {@link DatabaseAdmin->init()} for more information.
  *
  * @return bool
  */
 public static function database_is_ready()
 {
     // Used for unit tests
     if (self::$force_database_is_ready !== NULL) {
         return self::$force_database_is_ready;
     }
     if (self::$database_is_ready) {
         return self::$database_is_ready;
     }
     $requiredTables = ClassInfo::dataClassesFor('Member');
     $requiredTables[] = 'Group';
     $requiredTables[] = 'Permission';
     foreach ($requiredTables as $table) {
         // Skip test classes, as not all test classes are scaffolded at once
         if (is_subclass_of($table, 'TestOnly')) {
             continue;
         }
         // if any of the tables aren't created in the database
         if (!ClassInfo::hasTable($table)) {
             return false;
         }
         // HACK: DataExtensions aren't applied until a class is instantiated for
         // the first time, so create an instance here.
         singleton($table);
         // if any of the tables don't have all fields mapped as table columns
         $dbFields = DB::field_list($table);
         if (!$dbFields) {
             return false;
         }
         $objFields = DataObject::database_fields($table, false);
         $missingFields = array_diff_key($objFields, $dbFields);
         if ($missingFields) {
             return false;
         }
     }
     self::$database_is_ready = true;
     return true;
 }
 /**
  * Run the LinkCheckTask.
  * @todo Split functionality to separate methods
  */
 public function process()
 {
     if (class_exists('SapphireTest', false) && !SapphireTest::is_running_test()) {
         echo "\r\n";
     }
     if (!ClassInfo::hasTable('LinkCheckRun')) {
         if (!Director::is_ajax() && class_exists('SapphireTest', false) && !SapphireTest::is_running_test()) {
             echo "Database has not been built. Please run dev/build first!\r\n";
         }
         return false;
     }
     // If there is already a LinkCheckRun that exists and is not complete,
     // don't allow a new run as it could run the server to the ground!
     // @todo we probably want some system that allows cancelling a check halfway through
     if (DataObject::get_one('LinkCheckRun', "\"IsComplete\" = 0")) {
         if (!Director::is_ajax() && class_exists('SapphireTest', false) && !SapphireTest::is_running_test()) {
             echo "There is already a link check running at the moment. Please wait for it to complete before starting a new one.\r\n";
         }
         return false;
     }
     set_time_limit(0);
     ini_set('max_execution_time', 0);
     $goodLinks = 0;
     // 200-299 HTTP status codes
     $checkLinks = 0;
     // 300-399 HTTP status codes
     $brokenLinks = 0;
     // 400-599 HTTP status codes
     $pages = DataObject::get('SiteTree');
     if (!$pages) {
         return false;
     }
     $run = new LinkCheckRun();
     // We have started a new run, create the object and write it
     $run->write();
     $pagesChecked = 0;
     foreach ($pages as $page) {
         // Skip this page if it shouldn't be checked
         if (isset(self::$exempt_classes[get_class($page)])) {
             continue;
         }
         $processor = new LinkCheckProcessor($page->AbsoluteLink());
         if (Director::is_ajax()) {
             $processor->showMessages = false;
         }
         $results = $processor->run();
         // Memory cleanup - we don't need the processor anymore
         unset($processor);
         if ($results) {
             foreach ($results as $result) {
                 if ($result['Code'] >= 200 && $result['Code'] <= 299) {
                     $goodLinks++;
                 } elseif ($result['Code'] >= 300 && $result['Code'] <= 399) {
                     $checkLinks++;
                 } elseif ($result['Code'] >= 400 && $result['Code'] <= 599) {
                     $brokenLinks++;
                 }
                 // If the result is "Bad" (broken), create a BrokenLink record
                 if ($result['Code'] >= 400 && $result['Code'] <= 599) {
                     $brokenLink = new BrokenLink();
                     $brokenLink->Link = substr($result['Link'], 0, 255);
                     $brokenLink->Code = $result['Code'];
                     $brokenLink->Status = substr($result['Status'], 0, 30);
                     $brokenLink->LinkCheckRunID = $run->ID;
                     $brokenLink->PageID = $page->ID;
                     $brokenLink->write();
                     // Memory cleanup
                     $brokenLink->destroy();
                 }
             }
         }
         // Memory cleanup
         $page->destroy();
         $pagesChecked++;
     }
     // Memory cleanup
     unset($pages);
     // Mark as done - this is to indicate that the task has completed (for reporting in CMS)
     $run->FinishDate = date('Y-m-d H:i:s');
     $run->IsComplete = 1;
     $run->PagesChecked = $pagesChecked;
     $run->write();
     // Find the URL to the LinkCheckAdmin section in the CMS
     $linkcheckAdminLink = Director::absoluteBaseURL() . singleton('LinkCheckAdmin')->Link();
     // Count the number of BrokenLink records created for this run
     $runBrokenLinks = $run->BrokenLinks()->Count() ? $run->BrokenLinks()->Count() : 0;
     if (Director::is_ajax()) {
         return array('Date' => $run->obj('Created')->Nice(), 'LinkCheckRunID' => $run->ID);
     } elseif (Director::is_cli()) {
         if (class_exists('SapphireTest', false) && SapphireTest::is_running_test()) {
             return;
         }
         echo "SilverStripe Link Checker results\n";
         echo "---------------------------------\n\n";
         echo "{$pagesChecked} pages were checked for broken links.\n";
         echo "{$goodLinks} links were OK.\n";
         echo "{$checkLinks} links were redirected.\n";
         echo "{$brokenLinks} links were broken, and {$runBrokenLinks} BrokenLink records were generated for them.\n\n";
         echo "LinkCheckRun ID #{$run->ID} was created with {$runBrokenLinks} BrokenLink related records.\n";
         echo "Please visit {$linkcheckAdminLink} to see which broken links were found.\n\n";
     } else {
         if (class_exists('SapphireTest', false) && SapphireTest::is_running_test()) {
             return;
         }
         echo "<h1>SilverStripe Link Checker results</h1>";
         echo '<ul>';
         echo "<li>{$pagesChecked} pages were checked for broken links.</li>";
         echo "<li>{$goodLinks} links were OK.</li>";
         echo "<li>{$checkLinks} links were redirected.</li>";
         echo "<li>{$brokenLinks} links were broken, and {$runBrokenLinks} BrokenLink records were generated for them.</li>";
         echo '</ul>';
         echo "<p>LinkCheckRun ID #{$run->ID} was created with {$runBrokenLinks} BrokenLink related records.</p>";
         echo "<p>Please visit <a href=\"{$linkcheckAdminLink}\">{$linkcheckAdminLink}</a> to see which broken links were found.</p>";
     }
 }
 /**
  * @param SS_HTTPRequest $request
  * @return SS_HTTPResponse
  */
 public function handleRequest(SS_HTTPRequest $request, DataModel $model = null)
 {
     self::$is_at_root = true;
     $this->setDataModel($model);
     $this->pushCurrent();
     $this->init();
     if (!DB::isActive() || !ClassInfo::hasTable('SiteTree')) {
         $this->response = new SS_HTTPResponse();
         $this->response->redirect(Director::absoluteBaseURL() . 'dev/build?returnURL=' . (isset($_GET['url']) ? urlencode($_GET['url']) : null));
         return $this->response;
     }
     $request = new SS_HTTPRequest($request->httpMethod(), self::get_homepage_link() . '/', $request->getVars(), $request->postVars());
     $request->match('$URLSegment//$Action', true);
     $controller = new ModelAsController();
     $result = $controller->handleRequest($request, $model);
     $this->popCurrent();
     return $result;
 }
 public function handleRequest(SS_HTTPRequest $request, DataModel $model = null)
 {
     self::$is_at_root = true;
     $this->setDataModel($model);
     $this->pushCurrent();
     $this->init();
     if ($language = $request->param('Language')) {
         if (Config::inst()->get('MultilingualRootURLController', 'UseLocaleURL')) {
             if (Config::inst()->get('MultilingualRootURLController', 'UseDashLocale')) {
                 //Language is missing a dash 404
                 if (strpos($language, '-') === false) {
                     //Locale not found 404
                     if ($response = ErrorPage::response_for(404)) {
                         return $response;
                     } else {
                         $this->httpError(404, 'The requested page could not be found.');
                     }
                     return $this->response;
                 }
                 $locale = explode('-', $language);
                 $locale[1] = strtoupper($locale[1]);
                 //Make sure that the language is all lowercase
                 if ($language == implode('-', $locale)) {
                     //Locale not found 404
                     if ($response = ErrorPage::response_for(404)) {
                         return $response;
                     } else {
                         $this->httpError(404, 'The requested page could not be found.');
                     }
                     return $this->response;
                 }
                 $locale = implode('_', $locale);
             } else {
                 $locale = $language;
             }
         } else {
             if (strpos($request->param('Language'), '_') !== false) {
                 //Locale not found 404
                 if ($response = ErrorPage::response_for(404)) {
                     return $response;
                 } else {
                     $this->httpError(404, 'The requested page could not be found.');
                 }
                 return $this->response;
             } else {
                 $locale = i18n::get_locale_from_lang($language);
             }
         }
         if (in_array($locale, Translatable::get_allowed_locales())) {
             Cookie::set('language', $language);
             Translatable::set_current_locale($locale);
             i18n::set_locale($locale);
             if (!DB::isActive() || !ClassInfo::hasTable('SiteTree')) {
                 $this->response = new SS_HTTPResponse();
                 $this->response->redirect(Director::absoluteBaseURL() . 'dev/build?returnURL=' . (isset($_GET['url']) ? urlencode($_GET['url']) : null));
                 return $this->response;
             }
             $request->setUrl($language . '/' . self::get_homepage_link() . '/');
             $request->match('$Language/$URLSegment//$Action', true);
             $controller = new MultilingualModelAsController();
             $result = $controller->handleRequest($request, $model);
             $this->popCurrent();
             return $result;
         } else {
             //URL Param Locale is not allowed so redirect to default
             $this->redirect(Controller::join_links(Director::baseURL(), Config::inst()->get('MultilingualRootURLController', 'UseLocaleURL') ? Translatable::default_locale() : Translatable::default_lang()) . '/', 301);
             $this->popCurrent();
             return $this->response;
         }
     }
     //No Locale Param so detect browser language and redirect
     if ($locale = self::detect_browser_locale()) {
         if (Config::inst()->get('MultilingualRootURLController', 'UseLocaleURL')) {
             if (Config::inst()->get('MultilingualRootURLController', 'UseDashLocale')) {
                 $language = str_replace('_', '-', strtolower($locale));
             } else {
                 $language = $locale;
             }
         } else {
             $language = i18n::get_lang_from_locale($locale);
         }
         Cookie::set('language', $language);
         $this->redirect(Controller::join_links(Director::baseURL(), $language) . '/', 301);
         $this->popCurrent();
         return $this->response;
     }
     if (Config::inst()->get('MultilingualRootURLController', 'UseLocaleURL')) {
         if (Config::inst()->get('MultilingualRootURLController', 'UseDashLocale')) {
             $language = str_replace('_', '-', strtolower(Translatable::default_locale()));
         } else {
             $language = Translatable::default_locale();
         }
     } else {
         $language = Translatable::default_lang();
     }
     $this->redirect(Controller::join_links(Director::baseURL(), $language . '/'), 301);
     $this->popCurrent();
     return $this->response;
 }
Example #27
0
 /**
  * Initialisation function that is run before any action on the controller is called.
  */
 function init()
 {
     // Test and development sites should be secured, via basic-auth
     if (ClassInfo::hasTable("Group") && ClassInfo::hasTable("Member") && Director::isTest() && $this->basicAuthEnabled) {
         BasicAuth::requireLogin("SilverStripe test website.  Use your  CMS login", "ADMIN");
     }
     //
     Cookie::set("PastVisitor", true);
     // ClassInfo::hasTable() called to ensure that we're not in a very-first-setup stage
     if (ClassInfo::hasTable("Group") && ClassInfo::hasTable("Member") && ($member = Member::currentUser())) {
         Cookie::set("PastMember", true);
         DB::query("UPDATE Member SET LastVisited = NOW() WHERE ID = {$member->ID}", null);
     }
     // This is used to test that subordinate controllers are actually calling parent::init() - a common bug
     $this->baseInitCalled = true;
 }
Example #28
0
 /**
  * Writes all changes to this object to the database.
  *  - It will insert a record whenever ID isn't set, otherwise update.
  *  - All relevant tables will be updated.
  *  - $this->onBeforeWrite() gets called beforehand.
  *  - Extensions such as Versioned will ammend the database-write to ensure that a version is saved.
  *  - Calls to {@link DataObjectLog} can be used to see everything that's been changed.
  *
  * @param boolean $showDebug Show debugging information
  * @param boolean $forceInsert Run INSERT command rather than UPDATE, even if record already exists
  * @param boolean $forceWrite Write to database even if there are no changes
  *
  * @return int The ID of the record
  */
 public function write($showDebug = false, $forceInsert = false, $forceWrite = false)
 {
     $firstWrite = false;
     $this->brokenOnWrite = true;
     $isNewRecord = false;
     $this->onBeforeWrite();
     if ($this->brokenOnWrite) {
         user_error("{$this->class} has a broken onBeforeWrite() function.  Make sure that you call parent::onBeforeWrite().", E_USER_ERROR);
     }
     // New record = everything has changed
     if ($this->ID && is_numeric($this->ID) && !$forceInsert) {
         $dbCommand = 'update';
     } else {
         $dbCommand = 'insert';
         $this->changed = array();
         foreach ($this->record as $k => $v) {
             $this->changed[$k] = 2;
         }
         $firstWrite = true;
     }
     // No changes made
     if ($this->changed) {
         foreach ($this->getClassAncestry() as $ancestor) {
             if (ClassInfo::hasTable($ancestor)) {
                 $ancestry[] = $ancestor;
             }
         }
         // Look for some changes to make
         unset($this->changed['ID']);
         $hasChanges = false;
         foreach ($this->changed as $fieldName => $changed) {
             if ($changed) {
                 $hasChanges = true;
                 break;
             }
         }
         if ($hasChanges || $forceWrite || !$this->record['ID']) {
             // New records have their insert into the base data table done first, so that they can pass the
             // generated primary key on to the rest of the manipulation
             if (!$this->record['ID'] && isset($ancestry[0])) {
                 $baseTable = $ancestry[0];
                 DB::query("INSERT INTO `{$baseTable}` SET Created = NOW()");
                 $this->record['ID'] = DB::getGeneratedID($baseTable);
                 $this->changed['ID'] = 2;
                 $isNewRecord = true;
             }
             // Divvy up field saving into a number of database manipulations
             if (isset($ancestry) && is_array($ancestry)) {
                 foreach ($ancestry as $idx => $class) {
                     $classSingleton = singleton($class);
                     foreach ($this->record as $fieldName => $value) {
                         if (isset($this->changed[$fieldName]) && $this->changed[$fieldName] && ($fieldType = $classSingleton->fieldExists($fieldName))) {
                             $manipulation[$class]['fields'][$fieldName] = $value ? "'" . addslashes($value) . "'" : singleton($fieldType)->nullValue();
                         }
                     }
                     // Add the class name to the base object
                     if ($idx == 0) {
                         $manipulation[$class]['fields']["LastEdited"] = "now()";
                         if ($dbCommand == 'insert') {
                             $manipulation[$class]['fields']["Created"] = "now()";
                             //echo "<li>$this->class - " .get_class($this);
                             $manipulation[$class]['fields']["ClassName"] = "'{$this->class}'";
                         }
                     }
                     // In cases where there are no fields, this 'stub' will get picked up on
                     if (ClassInfo::hasTable($class)) {
                         $manipulation[$class]['command'] = $dbCommand;
                         $manipulation[$class]['id'] = $this->record['ID'];
                     } else {
                         unset($manipulation[$class]);
                     }
                 }
             }
             $this->extend('augmentWrite', $manipulation);
             // New records have their insert into the base data table done first, so that they can pass the
             // generated ID on to the rest of the manipulation
             if (isset($isNewRecord) && $isNewRecord && isset($manipulation[$baseTable])) {
                 $manipulation[$baseTable]['command'] = 'update';
             }
             DB::manipulate($manipulation);
             if (isset($isNewRecord) && $isNewRecord) {
                 DataObjectLog::addedObject($this);
             } else {
                 DataObjectLog::changedObject($this);
             }
             $this->changed = null;
         } elseif ($showDebug) {
             echo "<b>Debug:</b> no changes for DataObject<br />";
         }
         // Clears the cache for this object so get_one returns the correct object.
         $this->flushCache();
         if (!isset($this->record['Created'])) {
             $this->record['Created'] = date('Y-m-d H:i:s');
         }
         $this->record['LastEdited'] = date('Y-m-d H:i:s');
     }
     // Write ComponentSets as necessary
     if ($this->components) {
         foreach ($this->components as $component) {
             $component->write($firstWrite);
         }
     }
     return $this->record['ID'];
 }