/**
  * Saves a category group.
  *
  * @param CategoryGroupModel $group
  *
  * @throws \Exception
  * @return bool
  */
 public function saveGroup(CategoryGroupModel $group)
 {
     if ($group->id) {
         $groupRecord = CategoryGroupRecord::model()->findById($group->id);
         if (!$groupRecord) {
             throw new Exception(Craft::t('No category group exists with the ID “{id}”.', array('id' => $group->id)));
         }
         $oldCategoryGroup = CategoryGroupModel::populateModel($groupRecord);
         $isNewCategoryGroup = false;
     } else {
         $groupRecord = new CategoryGroupRecord();
         $isNewCategoryGroup = true;
     }
     // If they've set maxLevels to 0 (don't ask why), then pretend like there are none.
     if ($group->maxLevels == 0) {
         $group->maxLevels = null;
     }
     $groupRecord->name = $group->name;
     $groupRecord->handle = $group->handle;
     $groupRecord->hasUrls = $group->hasUrls;
     if ($group->hasUrls) {
         $groupRecord->template = $group->template;
     } else {
         $groupRecord->template = $group->template = null;
     }
     // Make sure that all of the URL formats are set properly
     $groupLocales = $group->getLocales();
     foreach ($groupLocales as $localeId => $groupLocale) {
         if ($group->hasUrls) {
             $urlFormatAttributes = array('urlFormat');
             $groupLocale->urlFormatIsRequired = true;
             if ($group->maxLevels == 1) {
                 $groupLocale->nestedUrlFormat = null;
             } else {
                 $urlFormatAttributes[] = 'nestedUrlFormat';
                 $groupLocale->nestedUrlFormatIsRequired = true;
             }
             foreach ($urlFormatAttributes as $attribute) {
                 if (!$groupLocale->validate(array($attribute))) {
                     $group->addError($attribute . '-' . $localeId, $groupLocale->getError($attribute));
                 }
             }
         } else {
             $groupLocale->urlFormat = null;
             $groupLocale->nestedUrlFormat = null;
         }
     }
     // Validate!
     $groupRecord->validate();
     $group->addErrors($groupRecord->getErrors());
     if (!$group->hasErrors()) {
         $transaction = craft()->db->getCurrentTransaction() === null ? craft()->db->beginTransaction() : null;
         try {
             // Create/update the structure
             if ($isNewCategoryGroup) {
                 $structure = new StructureModel();
             } else {
                 $structure = craft()->structures->getStructureById($oldCategoryGroup->structureId);
             }
             $structure->maxLevels = $group->maxLevels;
             craft()->structures->saveStructure($structure);
             $groupRecord->structureId = $structure->id;
             $group->structureId = $structure->id;
             // Is there a new field layout?
             $fieldLayout = $group->getFieldLayout();
             if (!$fieldLayout->id) {
                 // Delete the old one
                 if (!$isNewCategoryGroup && $oldCategoryGroup->fieldLayoutId) {
                     craft()->fields->deleteLayoutById($oldCategoryGroup->fieldLayoutId);
                 }
                 // Save the new one
                 craft()->fields->saveLayout($fieldLayout);
                 // Update the category group record/model with the new layout ID
                 $groupRecord->fieldLayoutId = $fieldLayout->id;
                 $group->fieldLayoutId = $fieldLayout->id;
             }
             // Save the category group
             $groupRecord->save(false);
             // Now that we have a category group ID, save it on the model
             if (!$group->id) {
                 $group->id = $groupRecord->id;
             }
             // Might as well update our cache of the category group while we have it.
             $this->_categoryGroupsById[$group->id] = $group;
             // Update the categorygroups_i18n table
             $newLocaleData = array();
             if (!$isNewCategoryGroup) {
                 // Get the old category group locales
                 $oldLocaleRecords = CategoryGroupLocaleRecord::model()->findAllByAttributes(array('groupId' => $group->id));
                 $oldLocales = CategoryGroupLocaleModel::populateModels($oldLocaleRecords, 'locale');
                 $changedLocaleIds = array();
             }
             foreach ($groupLocales as $localeId => $locale) {
                 // Was this already selected?
                 if (!$isNewCategoryGroup && isset($oldLocales[$localeId])) {
                     $oldLocale = $oldLocales[$localeId];
                     // Has the URL format changed?
                     if ($locale->urlFormat != $oldLocale->urlFormat || $locale->nestedUrlFormat != $oldLocale->nestedUrlFormat) {
                         craft()->db->createCommand()->update('categorygroups_i18n', array('urlFormat' => $locale->urlFormat, 'nestedUrlFormat' => $locale->nestedUrlFormat), array('id' => $oldLocale->id));
                         $changedLocaleIds[] = $localeId;
                     }
                 } else {
                     $newLocaleData[] = array($group->id, $localeId, $locale->urlFormat, $locale->nestedUrlFormat);
                 }
             }
             // Insert the new locales
             craft()->db->createCommand()->insertAll('categorygroups_i18n', array('groupId', 'locale', 'urlFormat', 'nestedUrlFormat'), $newLocaleData);
             if (!$isNewCategoryGroup) {
                 // Drop any locales that are no longer being used, as well as the associated category/element
                 // locale rows
                 $droppedLocaleIds = array_diff(array_keys($oldLocales), array_keys($groupLocales));
                 if ($droppedLocaleIds) {
                     craft()->db->createCommand()->delete('categorygroups_i18n', array('in', 'locale', $droppedLocaleIds));
                 }
             }
             // Finally, deal with the existing categories...
             if (!$isNewCategoryGroup) {
                 // Get all of the category IDs in this group
                 $criteria = craft()->elements->getCriteria(ElementType::Category);
                 $criteria->groupId = $group->id;
                 $criteria->status = null;
                 $criteria->limit = null;
                 $categoryIds = $criteria->ids();
                 // Should we be deleting
                 if ($categoryIds && $droppedLocaleIds) {
                     craft()->db->createCommand()->delete('elements_i18n', array('and', array('in', 'elementId', $categoryIds), array('in', 'locale', $droppedLocaleIds)));
                     craft()->db->createCommand()->delete('content', array('and', array('in', 'elementId', $categoryIds), array('in', 'locale', $droppedLocaleIds)));
                 }
                 // Are there any locales left?
                 if ($groupLocales) {
                     // Drop the old category URIs if the group no longer has URLs
                     if (!$group->hasUrls && $oldCategoryGroup->hasUrls) {
                         craft()->db->createCommand()->update('elements_i18n', array('uri' => null), array('in', 'elementId', $categoryIds));
                     } else {
                         if ($changedLocaleIds) {
                             foreach ($categoryIds as $categoryId) {
                                 craft()->config->maxPowerCaptain();
                                 // Loop through each of the changed locales and update all of the categories’ slugs and
                                 // URIs
                                 foreach ($changedLocaleIds as $localeId) {
                                     $criteria = craft()->elements->getCriteria(ElementType::Category);
                                     $criteria->id = $categoryId;
                                     $criteria->locale = $localeId;
                                     $criteria->status = null;
                                     $category = $criteria->first();
                                     // todo: replace the getContent()->id check with 'strictLocale' param once it's added
                                     if ($category && $category->getContent()->id) {
                                         craft()->elements->updateElementSlugAndUri($category, false, false);
                                     }
                                 }
                             }
                         }
                     }
                 }
             }
             if ($transaction !== null) {
                 $transaction->commit();
             }
         } catch (\Exception $e) {
             if ($transaction !== null) {
                 $transaction->rollback();
             }
             throw $e;
         }
         return true;
     } else {
         return false;
     }
 }