This is the form model
[spoiler]
<?php
namespace app\models\form;
use Yii;
use yii\base\Model;
use yii\web\UploadedFile;
use yii\helpers\StringHelper;
use yii\helpers\Inflector;
use app\models\Theme;
use app\models\Tag;
use app\models\ThemeTag;
/**
*
* @property Theme $theme
* @property ThemeTag $themeTag
* @property ThemeTags[] $themeTags
*/
class ThemeForm extends \yii\base\Model {
/**
*
* @var \app\models\Theme
*/
private $_theme;
/**
*
* @var \app\models\Tag
*/
private $_tags;
/**
*
* @var \app\models\ThemeTag[]
*/
private $_themetag;
/**
*
* {@inheritDoc}
* @see \yii\base\Model::rules()
*/
public function rules () {
return [
[['Theme', 'Tags'], 'required'],
];
}
public function beforeValidate() {
$this->theme->image = UploadedFile::getInstance($this->theme, 'image');
if(parent::beforeValidate()) {
return true;
}
return false;
}
/**
*
* {@inheritDoc}
* @see \yii\base\Model::afterValidate()
*/
public function afterValidate() {
if(!Model::validateMultiple($this->getAllModels())) {
$this->addError(null);
}
parent::afterValidate();
}
/**
* Save the form data
* @return boolean
*/
public function save () {
if(!$this->validate()) {
return false;
}
$transaction = Yii::$app->db->beginTransaction();
Yii::info('Theme ID beforeSave: ' . $this->theme->id);
if(!$this->theme->save()) {
Yii::info($this->theme->errors);
$transaction->rollBack();
return false;
}
Yii::info('Theme ID afterSave: ' . $this->theme->id);
if(!$this->saveTags()) {
$transaction->rollBack();
return false;
}
$transaction->commit();
return true;
}
/**
* Save the tags
* @return boolean
*/
public function saveTags() {
Yii::info(Theme::findOne(['id' => $this->theme->id]));
foreach($this->_tags as $key => $tag) {
if(!$existing_tag = Tag::findOne(['value' => $tag->value])) {
Yii::info('Save new tag: ' . $tag->title);
if(!$tag->save()) {
return false;
}
}
else {
$tag = $existing_tag;
}
$this->_themetag[$key]->tag_id = $tag->id;
$keep[] = $tag->id;
}
$query = ThemeTag::find()->andWhere(['theme_id' => $this->theme->id]);
if($keep) {
$query->andWhere(['not in', 'tag_id', $keep]);
}
foreach($query->all() as $themetag) {
$themetag->delete();
}
$used_tags = [];
foreach($this->_themetag as $themetag) {
$assigned = false;
foreach($this->theme->themeTags as $existing_tag) {
Yii::info('Existing tag: ' . $existing_tag->tag_id . ' Form tag: ' . $themetag->tag_id);
if($existing_tag->tag_id === $themetag->tag_id || in_array($themetag->tag_id, $used_tags)) {
Yii::info('is assigned ... set');
$assigned = true;
break;
}
}
if($assigned) {
Yii::info('is assigned ... continue');
continue;
}
$themetag->theme_id = $this->theme->id;
Yii::info('Theme ID: ' . $this->theme->id);
Yii::info('Save tag: ' . $themetag->tag_id . ' To Theme: ' . $this->theme->id);
Yii::info('ThemeTag Theme ID: ' . $themetag->theme_id);
if(!$themetag->save()) {
Yii::info($themetag->errors);
Yii::info($themetag->theme_id);
return false;
}
$used_tags[] = $themetag->tag_id;
}
return true;
}
/**
*
* @return \app\models\Theme
*/
public function getTheme () {
return $this->_theme;
}
/**
*
* @param array|\app\models\Theme $theme
*/
public function setTheme ($theme) {
if($theme instanceof Theme) {
$this->_theme = $theme;
}
else if (is_array($theme)) {
$this->_theme->setAttributes($theme);
}
}
/**
*
* @return \app\models\Tag
*/
public function getTag () {
$tag = new Tag;
return $tag;
}
/**
*
* @return array|\app\models\Tag[]
*/
public function getTags() {
if($this->_tags === null) {
$this->_tags = $this->theme->isNewRecord ? [] : $this->theme->themeTags;
}
return $this->_tags;
}
/**
* Get the theme tags and merge them into a CSV string
* @return string
*/
public function getTagsList() {
if($this->_tags === null) {
$this->_tags = $this->theme->isNewRecord ? [] : $this->theme->themeTags;
}
$tags = [];
foreach($this->_tags as $tag) {
if($tag instanceof Tag) {
$tags[] = $tag->title;
}
else {
$tags[] = $tag->tag->title;
}
}
return implode(', ', $tags);
}
/**
* Set an array of tags
*
* @param string|array|\app\models\Tag $tag
*/
public function setTags ($tags) {
if(is_array($tags)) {
$this->_tags = [];
foreach($tags as $key => $tag) {
$this->_tags[$key] = $this->getTag();
$this->_tags[$key]->setAttributes($tag);
$this->_themetag[$key] = $this->getThemeTag();
}
}
else if(is_string($tags)) {
$tags = StringHelper::explode($tags, ',', true, true);
$list = [];
foreach($tags as $tag) {
$list[] = [
'title' => $tag,
'value' => Inflector::slug($tag)
];
}
$this->setTags($list);
}
else if ($tags instanceof Tag) {
$this->_tags[$tags->id] = $tags;
}
}
public function getThemeTag() {
$themetag = new ThemeTag;
return $themetag;
}
/**
* Get the tags for the current theme
*
* @return \app\models\ThemeTag[]
*/
public function getThemeTags () {
if($this->_themetag === null) {
if($this->theme->isNewRecord) {
$this->_themetag[] = new ThemeTag();
}
else {
$this->_themetag = $this->theme->themeTags;
}
}
return $this->_themetag;
}
/**
* Set the tags for the current theme
*
* @param array|\app\models\ThemeTag $themetag
*/
public function setThemeTags ($themetag) {
if(is_array($themetag)) {
$this->themeTags->setAttributes($themetag);
}
else if ($themetag instanceof ThemeTag) {
$this->_themetag[] = $themetag;
}
}
/**
*
* @param \yii\widgets\ActiveForm $form
* @return string
*/
public function errorSummary ($form) {
$errorLists = [];
foreach($this->getAllModels() as $id => $model) {
$errorList = $form->errorSummary($model, [
'header' => '<p>Please fix the following errors for <b>'.$id.'</b></p>',
]);
$errorList = str_replace('<li></li>', '', $errorList);
$errorLists[] = $errorList;
}
return implode('', $errorLists);
}
/**
*
* @return NULL[]|\app\models\Tag[]|\yii\db\ActiveRecord[]|array[]
*/
private function getAllModels () {
$models = [
'Theme' => $this->theme,
];
foreach($this->tags as $id => $tag) {
$models['Tag.' . $id] = $tag;
}
foreach($this->themeTags as $key => $themeTag) {
$models['ThemeTag.' . $themeTag->id] = $themeTag;
}
return $models;
}
}
[/spoiler]
Theme model
[spoiler]
<?php
namespace app\models;
use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\SluggableBehavior;
/**
* This is the model class for table "theme".
*
* @property integer $id
* @property integer $user_id
* @property string $created_on
* @property resource $created_ip
* @property string $last_updated_on
* @property resource $last_update_ip
* @property string $title
* @property string $slug
* @property boolean $is_published
* @property string $published_on
* @property boolean $is_deleted
* @property string $deleted_on
* @property boolean $is_featured
* @property string $featured_on
* @property integer $review_count
* @property string $rating
* @property integer $installs
* @property string $display_image
* @property string $description
* @property string $template
* @property string $base_theme
* @property string $friend_style
* @property string $comment_style
* @property resource $sass
* @property resource $css
*
* @property UploadedFile $image
* @property Theme $featuredThemes
* @property ThemeReviews[] $themeReviews
* @property ThemeReview $themeReview
* @property User $user
* @property ThemeCategory[] $themeCategories
* @property ThemeSpotlight $themeSpotlight
* @property ThemeTag[] $themeTags
*/
class Theme extends \yii\db\ActiveRecord
{
/**
*
* @var string
*/
const SCENARIO_CREATE = 'create';
/**
*
* @var string
*/
const SCENARIO_EDIT = 'edit';
/**
*
* @var string
*/
const SCENARIO_PUBLISH = 'publish';
/**
*
* @var string
*/
const SCENARIO_FEATURE = 'feature';
/**
*
*/
const SCENARIO_RATING = 'rating';
/**
*
* @var \yii\web\UploadedFile
*/
public $image;
/**
* @inheritdoc
*/
public static function tableName()
{
return 'theme';
}
/**
* @inheritdoc
*/
public function rules()
{
$valid_ext = Yii::$app->params['theme_values']['image_extensions'];
$mime_types = Yii::$app->params['theme_values']['mime_types'];
return [
[['user_id', 'created_ip', 'last_update_ip', 'title', 'slug', 'display_image', 'description', 'sass', 'css'], 'required'],
[['user_id'], 'integer'],
[['created_on', 'last_updated_on', 'published_on', 'deleted_on', 'featured_on'], 'safe'],
[['is_published', 'is_deleted', 'is_featured'], 'boolean'],
[['rating', 'review_count'], 'number'],
[['template', 'base_theme', 'friend_style', 'comment_style', 'sass', 'css'], 'string'],
[['created_ip', 'last_update_ip'], 'string', 'max' => 16],
[['title', 'slug'], 'string', 'max' => 45],
[['display_image'], 'string', 'max' => 75],
[['description'], 'string', 'max' => 500],
[['user_id'], 'exist', 'skipOnError' => true, 'targetClass' => User::className(), 'targetAttribute' => ['user_id' => 'id']],
[['category_id'], 'exist', 'skipOnError' => true, 'targetClass' => Category::className(), 'targetAttribute' => ['category_id' => 'id']],
[['image'], 'image', 'skipOnEmpty' => false, 'message' => 'A display image is required', 'on' => self::SCENARIO_CREATE],
[['image'], 'image', 'minWidth' => 680, 'maxWidth' => 960,'minHeight' => 100, 'maxHeight' => 540],//, 'message' => 'The image must be 960x540'],
[['image'], 'image', 'extensions' => $valid_ext, 'mimeTypes' => $mime_types, 'message' => "Invalid file type; Accepted file types are {$valid_ext}"],
];
}
public function scenarios () {
$scenarios = parent::scenarios();
$scenarios[self::SCENARIO_CREATE] = [
'title', 'slug', 'description', 'category_id', 'created_on', 'created_ip', 'last_updated_on', 'last_update_ip',
'image', 'display_image' ,'template', 'base_theme', 'friend_style', 'comment_style', 'css', 'sass', 'category_id', 'user_id'];
$scenarios[self::SCENARIO_EDIT] = [
'title', 'slug', 'description', 'category_id', 'last_updated_on', 'last_update_ip',
'template', 'base_theme', 'friend_style', 'comment_style', 'css', 'sass', 'category_id', 'user_id'];
$scenarios[self::SCENARIO_PUBLISH] = [
'is_published', 'published_on'
];
$scenarios[self::SCENARIO_FEATURE] = [
'is_featured', 'featured_on'
];
$scenarios[self::SCENARIO_RATING] = [
'rating', 'review_count'
];
return $scenarios;
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'user_id' => 'User ID',
'created_on' => 'Created On',
'created_ip' => 'Created Ip',
'last_updated_on' => 'Last Updated On',
'last_update_ip' => 'Last Update Ip',
'title' => 'Title',
'slug' => 'Slug',
'is_published' => 'Is Published',
'published_on' => 'Published On',
'is_deleted' => 'Is Deleted',
'deleted_on' => 'Deleted On',
'is_featured' => 'Is Featured',
'featured_on' => 'Featured On',
'review_count' => 'Review Count',
'rating' => 'Rating',
'installs' => 'Installs',
'display_image' => 'Display Image',
'description' => 'Description',
'template' => 'Template',
'base_theme' => 'Base Theme',
'friend_style' => 'Friend Style',
'comment_style' => 'Comment Style',
'sass' => 'Sass',
'css' => 'Css',
'category_id' => 'Category',
];
}
public function beforeValidate() {
if($this->isNewRecord) {
$this->created_ip = inet_pton(Yii::$app->request->userIP);
$this->user_id = Yii::$app->user->id;
if(isset($this->image)) {
$this->display_image = $this->slug . '.' . $this->image->extension;
}
}
$this->last_update_ip = inet_pton(Yii::$app->request->userIP);
$sassc = SassC::createFile($this->sass)->setCompression('compact');
$sassc->process();
$this->css = trim($sassc->css);
$sassc->removeFile();
if(parent::beforeValidate()) {
return true;
}
return false;
}
public function behaviors() {
return [
[
'class' => SluggableBehavior::className(),
'attribute' => 'title',
'slugAttribute' => 'slug'
]
];
}
/**
* Upload the display image
* @return boolean
*/
public function saveFile() {
if($this->validate()) {
if(!is_dir(Yii::getAlias(Yii::$app->params['theme_upload_path']) . $this->slug)) {
mkdir(Yii::getAlias(Yii::$app->params['theme_upload_path']) . $this->slug);
}
return $this->image->saveAs(Yii::getAlias(Yii::$app->params['theme_upload_path']) . $this->slug . '/' . $this->display_image);
}
return false;
}
/**
*
* @param integer $limit
* @return \yii\db\ActiveQuery
*/
public static function findFeaturedThemes($limit = null) {
$query = self::find()->where(['is_featured' => true]);
if($limit !== null) {
$query->limit($limit);
}
return $query;
}
/**
* @return \yii\db\ActiveQuery
*/
public function getUser()
{
return $this->hasOne(User::className(), ['id' => 'user_id']);
}
/**
* @return \yii\db\ActiveQuery
*/
public function getThemeCategories()
{
return $this->hasMany(ThemeCategory::className(), ['theme_id' => 'id']);
}
/**
* @return \yii\db\ActiveQuery
*/
public function getThemeInstalls() {
return $this->hasMany(ThemeInstall::className(), ['theme_id' => 'id']);
}
public function getThemeReview() {
return $this->hasOne(ThemeReview::className(), ['theme_id' => 'id'])->where(['user_id' => Yii::$app->user->id]);
}
/**
* @return \yii\db\ActiveQuery
*/
public function getThemeReviews() {
return $this->hasMany(ThemeReview::className(), ['theme_id' => 'id']);
}
/**
* @return \yii\db\ActiveQuery
*/
public function getUsers()
{
return $this->hasMany(User::className(), ['id' => 'user_id'])->viaTable('theme_review', ['theme_id' => 'id']);
}
/**
* @throws \yii\base\NotSupportedException
* @return \yii\db\ActiveQuery
*/
public function getThemeSpotlight()
{
throw new NotSupportedException('Not implemented');
return $this->hasOne(ThemeSpotlight::className(), ['theme_id' => 'id']);
}
/**
* @return \yii\db\ActiveQuery
*/
public function getThemeTags()
{
return $this->hasMany(ThemeTag::className(), ['theme_id' => 'id']);
}
public static function find($is_published = true, $is_deleted = false) {
return parent::find()->where(['is_published' => $is_published, 'is_deleted' => $is_deleted]);
}
}
[/spoiler]
ThemeTag model
[spoiler]
<?php
namespace app\models;
use Yii;
/**
* This is the model class for table "theme_tag".
*
* @property integer $id
* @property integer $theme_id
* @property integer $tag_id
*
* @property User $tag
* @property Theme $theme
*/
class ThemeTag extends \yii\db\ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return 'theme_tag';
}
/**
* @inheritdoc
*/
public function rules()
{
return [
// [['theme_id', 'tag_id'], 'required'],
[['theme_id', 'tag_id'], 'integer'],
[['tag_id'], 'exist', 'skipOnError' => true, 'targetClass' => Tag::className(), 'targetAttribute' => ['tag_id' => 'id']],
[['theme_id'], 'exist', 'skipOnError' => true, 'targetClass' => Theme::className(), 'targetAttribute' => ['theme_id' => 'id']],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'theme_id' => 'Theme ID',
'tag_id' => 'Tag ID',
];
}
/**
* @return \yii\db\ActiveQuery
*/
public function getTag()
{
return $this->hasOne(Tag::className(), ['id' => 'tag_id']);
}
/**
* @return \yii\db\ActiveQuery
*/
public function getTheme()
{
return $this->hasOne(Theme::className(), ['id' => 'theme_id']);
}
}
[/spoiler]
And here’s the log from the transaction start to the rollback
https://imgur.com/CM0rY4N