/**
  * Returns the localization record for a given element and locale.
  *
  * @param int $elementId
  * @param string $locale
  */
 public function getElementLocaleRecord($elementId, $localeId)
 {
     return ElementLocaleRecord::model()->findByAttributes(array('elementId' => $elementId, 'locale' => $localeId));
 }
 /**
  * Saves an entry.
  *
  * @param EntryModel $entry
  * @throws Exception
  * @return bool
  */
 public function saveEntry(EntryModel $entry)
 {
     $isNewEntry = !$entry->id;
     // Entry data
     if (!$isNewEntry) {
         $entryRecord = EntryRecord::model()->with('element')->findById($entry->id);
         if (!$entryRecord) {
             throw new Exception(Craft::t('No entry exists with the ID “{id}”', array('id' => $entry->id)));
         }
         $elementRecord = $entryRecord->element;
         // if entry->sectionId is null and there is an entryRecord sectionId, we assume this is a front-end edit.
         if ($entry->sectionId === null && $entryRecord->sectionId) {
             $entry->sectionId = $entryRecord->sectionId;
         }
     } else {
         $entryRecord = new EntryRecord();
         $elementRecord = new ElementRecord();
         $elementRecord->type = ElementType::Entry;
     }
     $section = craft()->sections->getSectionById($entry->sectionId);
     if (!$section) {
         throw new Exception(Craft::t('No section exists with the ID “{id}”', array('id' => $entry->sectionId)));
     }
     $sectionLocales = $section->getLocales();
     if (!isset($sectionLocales[$entry->locale])) {
         throw new Exception(Craft::t('The section “{section}” is not enabled for the locale {locale}', array('section' => $section->name, 'locale' => $entry->locale)));
     }
     $entryRecord->sectionId = $entry->sectionId;
     $entryRecord->authorId = $entry->authorId;
     $entryRecord->postDate = $entry->postDate;
     $entryRecord->expiryDate = $entry->expiryDate;
     if ($entry->enabled && !$entryRecord->postDate) {
         // Default the post date to the current date/time
         $entryRecord->postDate = $entry->postDate = DateTimeHelper::currentUTCDateTime();
     }
     $entryRecord->validate();
     $entry->addErrors($entryRecord->getErrors());
     $elementRecord->enabled = $entry->enabled;
     $elementRecord->validate();
     $entry->addErrors($elementRecord->getErrors());
     // Entry locale data
     if ($entry->id) {
         $entryLocaleRecord = EntryLocaleRecord::model()->findByAttributes(array('entryId' => $entry->id, 'locale' => $entry->locale));
         // If entry->slug is null and there is an entryLocaleRecord slug, we assume this is a front-end edit.
         if ($entry->slug === null && $entryLocaleRecord->slug) {
             $entry->slug = $entryLocaleRecord->slug;
         }
     }
     if (empty($entryLocaleRecord)) {
         $entryLocaleRecord = new EntryLocaleRecord();
         $entryLocaleRecord->sectionId = $entry->sectionId;
         $entryLocaleRecord->locale = $entry->locale;
     }
     if ($entryLocaleRecord->isNewRecord() || $entry->slug != $entryLocaleRecord->slug) {
         $this->_generateEntrySlug($entry);
         $entryLocaleRecord->slug = $entry->slug;
     }
     $entryLocaleRecord->validate();
     $entry->addErrors($entryLocaleRecord->getErrors());
     // Element locale data
     if ($entry->id) {
         $elementLocaleRecord = ElementLocaleRecord::model()->findByAttributes(array('elementId' => $entry->id, 'locale' => $entry->locale));
     }
     if (empty($elementLocaleRecord)) {
         $elementLocaleRecord = new ElementLocaleRecord();
         $elementLocaleRecord->locale = $entry->locale;
     }
     if ($section->hasUrls && $entry->enabled) {
         // Make sure the section's URL format is valid. This shouldn't be possible due to section validation,
         // but it's not enforced by the DB, so anything is possible.
         $urlFormat = $sectionLocales[$entry->locale]->urlFormat;
         if (!$urlFormat || strpos($urlFormat, '{slug}') === false) {
             throw new Exception(Craft::t('The section “{section}” doesn’t have a valid URL Format.', array('section' => Craft::t($section->name))));
         }
         $elementLocaleRecord->uri = craft()->templates->renderObjectTemplate($urlFormat, $entry);
     } else {
         $elementLocaleRecord->uri = null;
     }
     $elementLocaleRecord->validate();
     $entry->addErrors($elementLocaleRecord->getErrors());
     // Entry content
     $fieldLayout = $section->getFieldLayout();
     $content = craft()->content->prepElementContentForSave($entry, $fieldLayout);
     $content->validate();
     $entry->addErrors($content->getErrors());
     if (!$entry->hasErrors()) {
         // Save the element record first
         $elementRecord->save(false);
         // Now that we have an element ID, save it on the other stuff
         if (!$entry->id) {
             $entry->id = $elementRecord->id;
             $entryRecord->id = $entry->id;
         }
         $entryRecord->save(false);
         $entryLocaleRecord->entryId = $entry->id;
         $elementLocaleRecord->elementId = $entry->id;
         $content->elementId = $entry->id;
         // Save the other records
         $entryLocaleRecord->save(false);
         $elementLocaleRecord->save(false);
         craft()->content->saveContent($content, false);
         // Update the search index
         craft()->search->indexElementAttributes($entry, $entry->locale);
         // Save a new version
         if (Craft::hasPackage(CraftPackage::PublishPro)) {
             craft()->entryRevisions->saveVersion($entry);
         }
         // Perform some post-save operations
         craft()->content->postSaveOperations($entry, $content);
         // Fire an 'onSaveEntry' event
         $this->onSaveEntry(new Event($this, array('entry' => $entry, 'isNewEntry' => $isNewEntry)));
         return true;
     } else {
         return false;
     }
 }
 /**
  * Handles all of the routine tasks that go along with saving elements.
  *
  * Those tasks include:
  *
  * - Validating its content (if $validateContent is `true`, or it’s left as `null` and the element is enabled)
  * - Ensuring the element has a title if its type {@link BaseElementType::hasTitles() has titles}, and giving it a
  *   default title in the event that $validateContent is set to `false`
  * - Saving a row in the `elements` table
  * - Assigning the element’s ID on the element model, if it’s a new element
  * - Assigning the element’s ID on the element’s content model, if there is one and it’s a new set of content
  * - Updating the search index with new keywords from the element’s content
  * - Setting a unique URI on the element, if it’s supposed to have one.
  * - Saving the element’s row(s) in the `elements_i18n` and `content` tables
  * - Deleting any rows in the `elements_i18n` and `content` tables that no longer need to be there
  * - Calling the field types’ {@link BaseFieldType::onAfterElementSave() onAfterElementSave()} methods
  * - Cleaing any template caches that the element was involved in
  *
  * This method should be called by a service’s “saveX()” method, _after_ it is done validating any attributes on
  * the element that are of particular concern to its element type. For example, if the element were an entry,
  * saveElement() should be called only after the entry’s sectionId and typeId attributes had been validated to
  * ensure that they point to valid section and entry type IDs.
  *
  * @param BaseElementModel $element         The element that is being saved
  * @param bool|null        $validateContent Whether the element's content should be validated. If left 'null', it
  *                                          will depend on whether the element is enabled or not.
  *
  * @throws Exception|\Exception
  * @return bool
  */
 public function saveElement(BaseElementModel $element, $validateContent = null)
 {
     $elementType = $this->getElementType($element->getElementType());
     $isNewElement = !$element->id;
     // Validate the content first
     if ($elementType->hasContent()) {
         if ($validateContent === null) {
             $validateContent = (bool) $element->enabled;
         }
         if ($validateContent && !craft()->content->validateContent($element)) {
             $element->addErrors($element->getContent()->getErrors());
             return false;
         } else {
             // Make sure there's a title
             if ($elementType->hasTitles()) {
                 $fields = array('title');
                 $content = $element->getContent();
                 $content->setRequiredFields($fields);
                 if (!$content->validate($fields) && $content->hasErrors('title')) {
                     // Just set *something* on it
                     if ($isNewElement) {
                         $content->title = 'New ' . $element->getClassHandle();
                     } else {
                         $content->title = $element->getClassHandle() . ' ' . $element->id;
                     }
                 }
             }
         }
     }
     // Get the element record
     if (!$isNewElement) {
         $elementRecord = ElementRecord::model()->findByAttributes(array('id' => $element->id, 'type' => $element->getElementType()));
         if (!$elementRecord) {
             throw new Exception(Craft::t('No element exists with the ID “{id}”.', array('id' => $element->id)));
         }
     } else {
         $elementRecord = new ElementRecord();
         $elementRecord->type = $element->getElementType();
     }
     // Set the attributes
     $elementRecord->enabled = (bool) $element->enabled;
     $elementRecord->archived = (bool) $element->archived;
     $transaction = craft()->db->getCurrentTransaction() === null ? craft()->db->beginTransaction() : null;
     try {
         // Fire an 'onBeforeSaveElement' event
         $event = new Event($this, array('element' => $element, 'isNewElement' => $isNewElement));
         $this->onBeforeSaveElement($event);
         // Is the event giving us the go-ahead?
         if ($event->performAction) {
             // Save the element record first
             $success = $elementRecord->save(false);
             if ($success) {
                 if ($isNewElement) {
                     // Save the element id on the element model, in case {id} is in the URL format
                     $element->id = $elementRecord->id;
                     if ($elementType->hasContent()) {
                         $element->getContent()->elementId = $element->id;
                     }
                 }
                 // Save the content
                 if ($elementType->hasContent()) {
                     craft()->content->saveContent($element, false, (bool) $element->id);
                 }
                 // Update the search index
                 craft()->search->indexElementAttributes($element);
                 // Update the locale records and content
                 // We're saving all of the element's locales here to ensure that they all exist and to update the URI in
                 // the event that the URL format includes some value that just changed
                 $localeRecords = array();
                 if (!$isNewElement) {
                     $existingLocaleRecords = ElementLocaleRecord::model()->findAllByAttributes(array('elementId' => $element->id));
                     foreach ($existingLocaleRecords as $record) {
                         $localeRecords[$record->locale] = $record;
                     }
                 }
                 $mainLocaleId = $element->locale;
                 $locales = $element->getLocales();
                 $localeIds = array();
                 if (!$locales) {
                     throw new Exception('All elements must have at least one locale associated with them.');
                 }
                 foreach ($locales as $localeId => $localeInfo) {
                     if (is_numeric($localeId) && is_string($localeInfo)) {
                         $localeId = $localeInfo;
                         $localeInfo = array();
                     }
                     $localeIds[] = $localeId;
                     if (!isset($localeInfo['enabledByDefault'])) {
                         $localeInfo['enabledByDefault'] = true;
                     }
                     if (isset($localeRecords[$localeId])) {
                         $localeRecord = $localeRecords[$localeId];
                     } else {
                         $localeRecord = new ElementLocaleRecord();
                         $localeRecord->elementId = $element->id;
                         $localeRecord->locale = $localeId;
                         $localeRecord->enabled = $localeInfo['enabledByDefault'];
                     }
                     // Is this the main locale?
                     $isMainLocale = $localeId == $mainLocaleId;
                     if ($isMainLocale) {
                         $localizedElement = $element;
                     } else {
                         // Copy the element for this locale
                         $localizedElement = $element->copy();
                         $localizedElement->locale = $localeId;
                         if ($localeRecord->id) {
                             // Keep the original slug
                             $localizedElement->slug = $localeRecord->slug;
                         } else {
                             // Default to the main locale's slug
                             $localizedElement->slug = $element->slug;
                         }
                     }
                     if ($elementType->hasContent()) {
                         if (!$isMainLocale) {
                             $content = null;
                             if (!$isNewElement) {
                                 // Do we already have a content row for this locale?
                                 $content = craft()->content->getContent($localizedElement);
                             }
                             if (!$content) {
                                 $content = craft()->content->createContent($localizedElement);
                                 $content->setAttributes($element->getContent()->getAttributes());
                                 $content->id = null;
                                 $content->locale = $localeId;
                             }
                             $localizedElement->setContent($content);
                         }
                         if (!$localizedElement->getContent()->id) {
                             craft()->content->saveContent($localizedElement, false, false);
                         }
                     }
                     // Capture the original slug, in case it's entirely composed of invalid characters
                     $originalSlug = $localizedElement->slug;
                     // Clean up the slug
                     ElementHelper::setValidSlug($localizedElement);
                     // If the slug was entirely composed of invalid characters, it will be blank now.
                     if ($originalSlug && !$localizedElement->slug) {
                         $localizedElement->slug = $originalSlug;
                         $element->addError('slug', Craft::t('{attribute} is invalid.', array('attribute' => Craft::t('Slug'))));
                         // Don't bother with any of the other locales
                         $success = false;
                         break;
                     }
                     ElementHelper::setUniqueUri($localizedElement);
                     $localeRecord->slug = $localizedElement->slug;
                     $localeRecord->uri = $localizedElement->uri;
                     if ($isMainLocale) {
                         $localeRecord->enabled = (bool) $element->localeEnabled;
                     }
                     $success = $localeRecord->save();
                     if (!$success) {
                         // Pass any validation errors on to the element
                         $element->addErrors($localeRecord->getErrors());
                         // Don't bother with any of the other locales
                         break;
                     }
                 }
                 if ($success) {
                     if (!$isNewElement) {
                         // Delete the rows that don't need to be there anymore
                         craft()->db->createCommand()->delete('elements_i18n', array('and', 'elementId = :elementId', array('not in', 'locale', $localeIds)), array(':elementId' => $element->id));
                         if ($elementType->hasContent()) {
                             craft()->db->createCommand()->delete($element->getContentTable(), array('and', 'elementId = :elementId', array('not in', 'locale', $localeIds)), array(':elementId' => $element->id));
                         }
                     }
                     // Call the field types' onAfterElementSave() methods
                     $fieldLayout = $element->getFieldLayout();
                     if ($fieldLayout) {
                         foreach ($fieldLayout->getFields() as $fieldLayoutField) {
                             $field = $fieldLayoutField->getField();
                             if ($field) {
                                 $fieldType = $field->getFieldType();
                                 if ($fieldType) {
                                     $fieldType->element = $element;
                                     $fieldType->onAfterElementSave();
                                 }
                             }
                         }
                     }
                     // Finally, delete any caches involving this element. (Even do this for new elements, since they
                     // might pop up in a cached criteria.)
                     craft()->templateCache->deleteCachesByElement($element);
                 }
             }
         } else {
             $success = false;
         }
         // Commit the transaction regardless of whether we saved the user, in case something changed
         // in onBeforeSaveElement
         if ($transaction !== null) {
             $transaction->commit();
         }
     } catch (\Exception $e) {
         if ($transaction !== null) {
             $transaction->rollback();
         }
         throw $e;
     }
     if ($success) {
         // Fire an 'onSaveElement' event
         $this->onSaveElement(new Event($this, array('element' => $element, 'isNewElement' => $isNewElement)));
     } else {
         if ($isNewElement) {
             $element->id = null;
             if ($elementType->hasContent()) {
                 $element->getContent()->id = null;
                 $element->getContent()->elementId = null;
             }
         }
     }
     return $success;
 }