1 | <?php |
||||
2 | /** |
||||
3 | * @link http://www.yiiframework.com/ |
||||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||||
5 | * @license http://www.yiiframework.com/license/ |
||||
6 | */ |
||||
7 | |||||
8 | namespace yii\behaviors; |
||||
9 | |||||
10 | use Yii; |
||||
11 | use yii\base\InvalidConfigException; |
||||
12 | use yii\db\BaseActiveRecord; |
||||
13 | use yii\helpers\ArrayHelper; |
||||
14 | use yii\helpers\Inflector; |
||||
15 | use yii\validators\UniqueValidator; |
||||
16 | |||||
17 | /** |
||||
18 | * SluggableBehavior automatically fills the specified attribute with a value that can be used a slug in a URL. |
||||
19 | * |
||||
20 | * Note: This behavior relies on php-intl extension for transliteration. If it is not installed it |
||||
21 | * falls back to replacements defined in [[\yii\helpers\Inflector::$transliteration]]. |
||||
22 | * |
||||
23 | * To use SluggableBehavior, insert the following code to your ActiveRecord class: |
||||
24 | * |
||||
25 | * ```php |
||||
26 | * use yii\behaviors\SluggableBehavior; |
||||
27 | * |
||||
28 | * public function behaviors() |
||||
29 | * { |
||||
30 | * return [ |
||||
31 | * [ |
||||
32 | * 'class' => SluggableBehavior::class, |
||||
33 | * 'attribute' => 'title', |
||||
34 | * // 'slugAttribute' => 'slug', |
||||
35 | * ], |
||||
36 | * ]; |
||||
37 | * } |
||||
38 | * ``` |
||||
39 | * |
||||
40 | * By default, SluggableBehavior will fill the `slug` attribute with a value that can be used a slug in a URL |
||||
41 | * when the associated AR object is being validated. |
||||
42 | * |
||||
43 | * Because attribute values will be set automatically by this behavior, they are usually not user input and should therefore |
||||
44 | * not be validated, i.e. the `slug` attribute should not appear in the [[\yii\base\Model::rules()|rules()]] method of the model. |
||||
45 | * |
||||
46 | * If your attribute name is different, you may configure the [[slugAttribute]] property like the following: |
||||
47 | * |
||||
48 | * ```php |
||||
49 | * public function behaviors() |
||||
50 | * { |
||||
51 | * return [ |
||||
52 | * [ |
||||
53 | * 'class' => SluggableBehavior::class, |
||||
54 | * 'slugAttribute' => 'alias', |
||||
55 | * ], |
||||
56 | * ]; |
||||
57 | * } |
||||
58 | * ``` |
||||
59 | * |
||||
60 | * @author Alexander Kochetov <[email protected]> |
||||
61 | * @author Paul Klimov <[email protected]> |
||||
62 | * @since 2.0 |
||||
63 | */ |
||||
64 | class SluggableBehavior extends AttributeBehavior |
||||
65 | { |
||||
66 | /** |
||||
67 | * @var string the attribute that will receive the slug value |
||||
68 | */ |
||||
69 | public $slugAttribute = 'slug'; |
||||
70 | /** |
||||
71 | * @var string|array|null the attribute or list of attributes whose value will be converted into a slug |
||||
72 | * or `null` meaning that the `$value` property will be used to generate a slug. |
||||
73 | */ |
||||
74 | public $attribute; |
||||
75 | /** |
||||
76 | * @var callable|string|null the value that will be used as a slug. This can be an anonymous function |
||||
77 | * or an arbitrary value or null. If the former, the return value of the function will be used as a slug. |
||||
78 | * If `null` then the `$attribute` property will be used to generate a slug. |
||||
79 | * The signature of the function should be as follows, |
||||
80 | * |
||||
81 | * ```php |
||||
82 | * function ($event) |
||||
83 | * { |
||||
84 | * // return slug |
||||
85 | * } |
||||
86 | * ``` |
||||
87 | */ |
||||
88 | public $value; |
||||
89 | /** |
||||
90 | * @var bool whether to generate a new slug if it has already been generated before. |
||||
91 | * If true, the behavior will not generate a new slug even if [[attribute]] is changed. |
||||
92 | * @since 2.0.2 |
||||
93 | */ |
||||
94 | public $immutable = false; |
||||
95 | /** |
||||
96 | * @var bool whether to ensure generated slug value to be unique among owner class records. |
||||
97 | * If enabled behavior will validate slug uniqueness automatically. If validation fails it will attempt |
||||
98 | * generating unique slug value from based one until success. |
||||
99 | */ |
||||
100 | public $ensureUnique = false; |
||||
101 | /** |
||||
102 | * @var bool whether to skip slug generation if [[attribute]] is null or an empty string. |
||||
103 | * If true, the behaviour will not generate a new slug if [[attribute]] is null or an empty string. |
||||
104 | * @since 2.0.13 |
||||
105 | */ |
||||
106 | public $skipOnEmpty = false; |
||||
107 | /** |
||||
108 | * @var array configuration for slug uniqueness validator. Parameter 'class' may be omitted - by default |
||||
109 | * [[UniqueValidator]] will be used. |
||||
110 | * @see UniqueValidator |
||||
111 | */ |
||||
112 | public $uniqueValidator = []; |
||||
113 | /** |
||||
114 | * @var callable slug unique value generator. It is used in case [[ensureUnique]] enabled and generated |
||||
115 | * slug is not unique. This should be a PHP callable with following signature: |
||||
116 | * |
||||
117 | * ```php |
||||
118 | * function ($baseSlug, $iteration, $model) |
||||
119 | * { |
||||
120 | * // return uniqueSlug |
||||
121 | * } |
||||
122 | * ``` |
||||
123 | * |
||||
124 | * If not set unique slug will be generated adding incrementing suffix to the base slug. |
||||
125 | */ |
||||
126 | public $uniqueSlugGenerator; |
||||
127 | |||||
128 | |||||
129 | /** |
||||
130 | * {@inheritdoc} |
||||
131 | */ |
||||
132 | 9 | public function init() |
|||
133 | { |
||||
134 | 9 | parent::init(); |
|||
135 | |||||
136 | 9 | if (empty($this->attributes)) { |
|||
137 | 9 | $this->attributes = [BaseActiveRecord::EVENT_BEFORE_VALIDATE => $this->slugAttribute]; |
|||
138 | } |
||||
139 | |||||
140 | 9 | if ($this->attribute === null && $this->value === null) { |
|||
141 | throw new InvalidConfigException('Either "attribute" or "value" property must be specified.'); |
||||
142 | } |
||||
143 | 9 | } |
|||
144 | |||||
145 | /** |
||||
146 | * {@inheritdoc} |
||||
147 | */ |
||||
148 | 9 | protected function getValue($event) |
|||
149 | { |
||||
150 | 9 | if (!$this->isNewSlugNeeded()) { |
|||
151 | 3 | return $this->owner->{$this->slugAttribute}; |
|||
152 | } |
||||
153 | |||||
154 | 9 | if ($this->attribute !== null) { |
|||
155 | 8 | $slugParts = []; |
|||
156 | 8 | foreach ((array) $this->attribute as $attribute) { |
|||
157 | 8 | $part = ArrayHelper::getValue($this->owner, $attribute); |
|||
158 | 8 | if ($this->skipOnEmpty && $this->isEmpty($part)) { |
|||
159 | 1 | return $this->owner->{$this->slugAttribute}; |
|||
160 | } |
||||
161 | 8 | $slugParts[] = $part; |
|||
162 | } |
||||
163 | 8 | $slug = $this->generateSlug($slugParts); |
|||
164 | } else { |
||||
165 | 1 | $slug = parent::getValue($event); |
|||
166 | } |
||||
167 | |||||
168 | 9 | return $this->ensureUnique ? $this->makeUnique($slug) : $slug; |
|||
169 | } |
||||
170 | |||||
171 | /** |
||||
172 | * Checks whether the new slug generation is needed |
||||
173 | * This method is called by [[getValue]] to check whether the new slug generation is needed. |
||||
174 | * You may override it to customize checking. |
||||
175 | * @return bool |
||||
176 | * @since 2.0.7 |
||||
177 | */ |
||||
178 | 9 | protected function isNewSlugNeeded() |
|||
179 | { |
||||
180 | 9 | if (empty($this->owner->{$this->slugAttribute})) { |
|||
181 | 9 | return true; |
|||
182 | } |
||||
183 | |||||
184 | 4 | if ($this->immutable) { |
|||
185 | 2 | return false; |
|||
186 | } |
||||
187 | |||||
188 | 2 | if ($this->attribute === null) { |
|||
189 | return true; |
||||
190 | } |
||||
191 | |||||
192 | 2 | foreach ((array) $this->attribute as $attribute) { |
|||
193 | 2 | if ($this->owner->isAttributeChanged($attribute)) { |
|||
0 ignored issues
–
show
The method
isAttributeChanged() does not exist on yii\base\Component . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
194 | 2 | return true; |
|||
195 | } |
||||
196 | } |
||||
197 | |||||
198 | 1 | return false; |
|||
199 | } |
||||
200 | |||||
201 | /** |
||||
202 | * This method is called by [[getValue]] to generate the slug. |
||||
203 | * You may override it to customize slug generation. |
||||
204 | * The default implementation calls [[\yii\helpers\Inflector::slug()]] on the input strings |
||||
205 | * concatenated by dashes (`-`). |
||||
206 | * @param array $slugParts an array of strings that should be concatenated and converted to generate the slug value. |
||||
207 | * @return string the conversion result. |
||||
208 | */ |
||||
209 | 8 | protected function generateSlug($slugParts) |
|||
210 | { |
||||
211 | 8 | return Inflector::slug(implode('-', $slugParts)); |
|||
212 | } |
||||
213 | |||||
214 | /** |
||||
215 | * This method is called by [[getValue]] when [[ensureUnique]] is true to generate the unique slug. |
||||
216 | * Calls [[generateUniqueSlug]] until generated slug is unique and returns it. |
||||
217 | * @param string $slug basic slug value |
||||
218 | * @return string unique slug |
||||
219 | * @see getValue |
||||
220 | * @see generateUniqueSlug |
||||
221 | * @since 2.0.7 |
||||
222 | */ |
||||
223 | 4 | protected function makeUnique($slug) |
|||
224 | { |
||||
225 | 4 | $uniqueSlug = $slug; |
|||
226 | 4 | $iteration = 0; |
|||
227 | 4 | while (!$this->validateSlug($uniqueSlug)) { |
|||
228 | 2 | $iteration++; |
|||
229 | 2 | $uniqueSlug = $this->generateUniqueSlug($slug, $iteration); |
|||
230 | } |
||||
231 | |||||
232 | 4 | return $uniqueSlug; |
|||
233 | } |
||||
234 | |||||
235 | /** |
||||
236 | * Checks if given slug value is unique. |
||||
237 | * @param string $slug slug value |
||||
238 | * @return bool whether slug is unique. |
||||
239 | */ |
||||
240 | 4 | protected function validateSlug($slug) |
|||
241 | { |
||||
242 | /* @var $validator UniqueValidator */ |
||||
243 | /* @var $model BaseActiveRecord */ |
||||
244 | 4 | $validator = Yii::createObject(array_merge( |
|||
245 | [ |
||||
246 | 4 | 'class' => UniqueValidator::className(), |
|||
247 | ], |
||||
248 | 4 | $this->uniqueValidator |
|||
249 | )); |
||||
250 | |||||
251 | 4 | $model = clone $this->owner; |
|||
252 | 4 | $model->clearErrors(); |
|||
253 | 4 | $model->{$this->slugAttribute} = $slug; |
|||
254 | |||||
255 | 4 | $validator->validateAttribute($model, $this->slugAttribute); |
|||
256 | 4 | return !$model->hasErrors(); |
|||
257 | } |
||||
258 | |||||
259 | /** |
||||
260 | * Generates slug using configured callback or increment of iteration. |
||||
261 | * @param string $baseSlug base slug value |
||||
262 | * @param int $iteration iteration number |
||||
263 | * @return string new slug value |
||||
264 | * @throws \yii\base\InvalidConfigException |
||||
265 | */ |
||||
266 | 2 | protected function generateUniqueSlug($baseSlug, $iteration) |
|||
267 | { |
||||
268 | 2 | if (is_callable($this->uniqueSlugGenerator)) { |
|||
269 | 1 | return call_user_func($this->uniqueSlugGenerator, $baseSlug, $iteration, $this->owner); |
|||
270 | } |
||||
271 | |||||
272 | 1 | return $baseSlug . '-' . ($iteration + 1); |
|||
273 | } |
||||
274 | |||||
275 | /** |
||||
276 | * Checks if $slugPart is empty string or null. |
||||
277 | * |
||||
278 | * @param string $slugPart One of attributes that is used for slug generation. |
||||
279 | * @return bool whether $slugPart empty or not. |
||||
280 | * @since 2.0.13 |
||||
281 | */ |
||||
282 | 1 | protected function isEmpty($slugPart) |
|||
283 | { |
||||
284 | 1 | return $slugPart === null || $slugPart === ''; |
|||
285 | } |
||||
286 | } |
||||
287 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.