Categories::afterSave()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 12
nc 2
nop 0
1
<?php
2
3
/**
4
 * This is the model class for table "categories".
5
 *
6
 * The followings are the available columns in table 'categories':
7
 * @property integer $id
8
 * @property integer $parent_id
9
 * @property string $name
10
 * @property string $slug
11
 * @property string $created
12
 * @property string $updated
13
 *
14
 * The followings are the available model relations:
15
 * @property Categories $parent
16
 * @property Categories[] $categories
17
 * @property CategoriesMetadata[] $categoriesMetadatas
18
 * @property Content[] $contents
19
 */
20
class Categories extends CiiModel
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
21
{
22
	public $pageSize = 15;
23
24
	public $description = NULL;
25
26
	public $keywords = array();
27
28
	/**
29
	 * Returns the static model of the specified AR class.
30
	 * @param string $className active record class name.
31
	 * @return Categories the static model class
32
	 */
33
	public static function model($className=__CLASS__)
34
	{
35
		return parent::model($className);
36
	}
37
38
	/**
39
	 * @return string the associated database table name
40
	 */
41
	public function tableName()
42
	{
43
		return 'categories';
44
	}
45
46
	/**
47
	 * @return array validation rules for model attributes.
48
	 */
49
	public function rules()
50
	{
51
		// NOTE: you should only define rules for those attributes that
52
		// will receive user inputs.
53
		return array(
54
			array('parent_id, name', 'required'),
55
			array('parent_id', 'numerical', 'integerOnly'=>true),
56
			array('name, slug', 'length', 'max'=>150),
57
			// The following rule is used by search().
58
			// Please remove those attributes that should not be searched.
59
			array('id, parent_id, name, slug', 'safe', 'on'=>'search'),
60
		);
61
	}
62
63
	/**
64
	 * @return array relational rules.
65
	 */
66
	public function relations()
67
	{
68
		// NOTE: you may need to adjust the relation name and the related
69
		// class name for the relations automatically generated below.
70
		return array(
71
			'parent' => array(self::BELONGS_TO, 'Categories', 'parent_id'),
72
			'metadata' => array(self::HAS_MANY, 'CategoriesMetadata', 'category_id'),
73
			'content' => array(self::HAS_MANY, 'Content', 'category_id'),
74
		);
75
	}
76
77
	/**
78
	 * @return array customized attribute labels (name=>label)
79
	 */
80
	public function attributeLabels()
81
	{
82
		return array(
83
			'id' 		=> Yii::t('ciims.models.Categories', 'ID'),
84
			'parent_id' => Yii::t('ciims.models.Categories', 'Parent'),
85
			'name'      => Yii::t('ciims.models.Categories', 'Name'),
86
			'slug'      => Yii::t('ciims.models.Categories', 'Slug'),
87
			'description' => Yii::t('ciims.models.Categories', 'Description'),
88
			'created'   => Yii::t('ciims.models.Categories', 'Created'),
89
			'updated'   => Yii::t('ciims.models.Categories', 'Updated'),
90
		);
91
	}
92
93
	public function getDescription()
94
	{
95
		$this->description = CategoriesMetadata::model()->findByAttributes(array('category_id' => $this->id, 'key' => 'description'));
96
		if ($this->description == null || $this->description == false)
97
			return NULL;
98
99
		$this->description = $this->description->value;
100
		return $this->description;
101
	}
102
103
	/**
104
	 * Retrieves a list of models based on the current search/filter conditions.
105
	 * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
106
	 */
107
	public function search()
108
	{
109
		$criteria=new CDbCriteria;
110
111
		$criteria->compare('id',$this->id);
112
		$criteria->compare('parent_id',$this->parent_id);
113
		$criteria->compare('name',$this->name,true);
114
		$criteria->compare('slug',$this->slug,true);
115
		$criteria->compare('created',$this->created,true);
116
		$criteria->compare('updated',$this->updated,true);
117
		$criteria->order = "id DESC";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal id DESC does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
118
119
		return new CActiveDataProvider($this, array(
120
			'criteria' => $criteria,
121
			'pagination' => array(
122
				'pageSize' => $this->pageSize
123
			)
124
		));
125
	}
126
127
	/**
128
	 * Gets keyword tags for this entry
129
	 * @return array
130
	 */
131
	public function getKeywords()
132
	{
133
		$tags = CategoriesMetadata::model()->findByAttributes(array('category_id' => $this->id, 'key' => 'keywords'));
134
		return $tags === NULL ? array() : CJSON::decode($tags->value);
135
	}
136
137
	/**
138
	 * Adds a tag to the model
139
	 * @param string $tag	The tag to add
140
	 * @return bool			If the insert was successful or not
141
	 */
142
	public function addKeyword($tag)
143
	{
144
		$tags = $this->keywords;
145
		if (in_array($tag, $tags)  || $tag == "")
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
146
			return false;
147
148
		$tags[] = $tag;
149
		$tags = CJSON::encode($tags);
150
		$metaTag = CategoriesMetadata::model()->findByAttributes(array('category_id' => $this->id, 'key' => 'keywords'));
151
		if ($metaTag == false || $metaTag == NULL)
152
		{
153
			$metaTag = new CategoriestMetadata;
154
			$metaTag->content_id = $this->id;
155
			$metaTag->key = 'keywords';
156
		}
157
158
		$metaTag->value = $tags;
159
		return $metaTag->save();
160
	}
161
162
	/**
163
	 * Removes a tag from the model
164
	 * @param string $tag	The tag to remove
165
	 * @return bool			If the removal was successful
166
	 */
167
	public function removeKeyword($tag)
168
	{
169
		$tags = $this->keywords;
170
		if (!in_array($tag, $tags) || $tag == "")
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
171
			return false;
172
173
		$key = array_search($tag, $tags);
174
		unset($tags[$key]);
175
		$tags = CJSON::encode($tags);
176
177
		$metaTag = CategoryMetadata::model()->findByAttributes(array('category_id' => $this->id, 'key' => 'keywords'));
178
		$metaTag->value = $tags;
179
		return $metaTag->save();
180
	}
181
182
	/**
183
	 * Verifies the slug before validating the model
184
	 */
185
	public function beforeValidate()
186
	{
187
		$this->slug = $this->verifySlug($this->slug, $this->name);
188
189
		return parent::beforeValidate();
190
	}
191
192
	/**
193
	 * Flushes URL data from the cache before the model is updated
194
	 */
195
	public function afterSave()
196
	{
197
		$meta = CategoriesMetadata::model()->findByAttributes(array('category_id' => $this->id, 'key' => 'description'));
198
		if ($meta == NULL)
199
			$meta = new CategoriesMetadata;
200
201
		$meta->category_id = $this->id;
202
		$meta->key = 'description';
203
		$meta->value  = $this->description;
204
205
		$meta->save();
206
207
		Yii::app()->cache->delete('CiiMS::Content::list');
208
		Yii::app()->cache->delete('CiiMS::Routes');
209
		Yii::app()->cache->delete('categories-pid');
210
211
		return parent::afterSave();
212
	}
213
214
	/**
215
	 * Automatically corrects parent tree issues that arise when a parent category node
216
	 * is deleted.
217
	 * @return boolean
218
	 */
219
	public function beforeDelete()
220
	{
221
		// Prevents the main "uncategorized category from being deleted"
222
		if ($this->id == 1)
223
		{
224
			Yii::app()->user->setFlash('error', Yii::t('ciims.models.Categories', 'This category cannot be deleted'));
225
			return false;
226
		}
227
228
		Yii::app()->cache->delete('CiiMS::Content::list');
229
		Yii::app()->cache->delete('CiiMS::Routes');
230
		Yii::app()->cache->delete('categories-pid');
231
232
		$parent = $this->parent_id;
233
		$id = $this->id;
234
235
		// Reassign all posts to the parent category
236
		Yii::app()->db
237
				->createCommand('UPDATE content SET category_id = :parent_id WHERE category_id = :id')
238
				->bindParam(':parent_id', $parent)
239
				->bindParam(':id', $id)
240
				->execute();
241
242
		// Reassign all child categories to the parent category
243
		$data = $this->findAllByAttributes(array('parent_id' => $id));
244
245
		foreach ($data as $row)
246
		{
247
			$id = $row->id;
248
			Yii::app()->db
249
					->createCommand('UPDATE categories SET parent_id = :parent_id WHERE id = :id')
250
					->bindParam(':parent_id', $parent)
251
					->bindParam(':id', $id)
252
					->execute();
253
		}
254
255
		return parent::beforeDelete();
256
	}
257
258
	/**
259
	 * Retrieves the parent categories for a given category_id
260
	 * @param  int $id
261
	 * @return array
262
	 */
263
	public function getParentCategories($id)
264
	{
265
		// Retrieve the data from cache if necessary
266
		$response = Yii::app()->cache->get('categories-pid');
267
		if ($response == NULL)
268
		{
269
			$response = Yii::app()->db->createCommand('SELECT id, parent_id, name, slug FROM categories')->queryAll();
270
			Yii::app()->cache->set('categories-pid', $response);
271
		}
272
273
		return $this->_getParentCategories($response, $id);
274
	}
275
276
	/**
277
	 * Recursive callback for retrieving parent categories
278
	 * @param  int  $id      The category we're seeking
279
	 * @param  array  $stack A stack to hold the entire tree
280
	 * @return array
281
	 */
282
	private function _getParentCategories($all_categories, $id, array $stack = array())
283
	{
284
		if ($id == 1)
285
			return array_reverse($stack);
286
287
		foreach ($all_categories as $k=>$v)
288
		{
289
			if ($v['id'] == $id)
290
			{
291
				$stack[$v['name']] = array(str_replace(Yii::app()->baseUrl, NULL, Yii::app()->createUrl($v['slug'])));
292
				return $this->_getParentCategories($all_categories, $v['parent_id'], $stack);
293
			}
294
		}
295
	}
296
297
	/**
298
	 * checkSlug - Recursive method to verify that the slug can be used
299
	 * This method is purposfuly declared here to so that Content::findByPk is used instead of CiiModel::findByPk
300
	 * @param string $slug - the slug to be checked
301
	 * @param int $id - the numeric id to be appended to the slug if a conflict exists
302
	 * @return string $slug - the final slug to be used
303
	 */
304
	public function checkSlug($slug, $id=NULL)
305
	{
306
		$content = false;
307
308
		// Find the number of items that have the same slug as this one
309
		$count = $this->countByAttributes(array('slug'=>$slug . $id));
310
311
		if ($count == 0)
312
		{
313
			$content = true;
314
			$count = Content::model()->countByAttributes(array('slug'=>$slug . $id));
315
		}
316
317
		// If we found an item that matched, it's possible that it is the current item (or a previous version of it)
318
		// in which case we don't need to alter the slug
319
		if ($count >= 1)
320
		{
321
			if ($content)
322
				return $this->checkSlug($slug, ($id === NULL ? 1 : ($id+1)));
323
324
			// Pull the data that matches
325
			$data = $this->findByPk($this->id === NULL ? -1 : $this->id);
326
327
			// Check the pulled data id to the current item
328
			if ($data !== NULL && $data->id == $this->id)
329
				return $slug;
330
		}
331
332
		if ($count == 0 && !in_array($slug, $this->forbiddenRoutes))
333
			return $slug . $id;
334
		else
335
			return $this->checkSlug($slug, ($id === NULL ? 1 : ($id+1)));
336
	}
337
}
338