| @@ -20,18 +20,47 @@ | ||
| 20 | 20 | * |
| 21 | 21 | * @package FluentCrm\App\Models |
| 22 | 22 | * |
| 23 | 23 | * @version 1.1.0 |
| 24 | + * | |
| 25 | + * @property int $id | |
| 26 | + * @property int|null $user_id | |
| 27 | + * @property string|null $title | |
| 28 | + * @property string|null $slug | |
| 29 | + * @property string|null $message | |
| 30 | + * @property string|null $message_rendered | |
| 31 | + * @property string|null $type | |
| 32 | + * @property int|null $space_id | |
| 33 | + * @property string|null $privacy | |
| 34 | + * @property string|null $status | |
| 35 | + * @property string|null $featured_image | |
| 36 | + * @property int|null $is_sticky | |
| 37 | + * @property string|null $scheduled_at | |
| 38 | + * @property string|null $expired_at | |
| 39 | + * @property string|null $content_type | |
| 40 | + * @property int $comments_count | |
| 41 | + * @property int $reactions_count | |
| 42 | + * @property array $meta | |
| 43 | + * @property int|null $priority | |
| 44 | + * @property int|null $parent_id | |
| 45 | + * @property string|null $created_at | |
| 46 | + * @property string|null $updated_at | |
| 47 | + * @property-read array $questions | |
| 48 | + * @property-read array $enabled_questions | |
| 49 | + * @property-read bool $is_enforce_pass | |
| 50 | + * @property-read bool $is_free_preview | |
| 51 | + * @property-read int $passing_score | |
| 52 | + * @property-read Course|null $course | |
| 24 | 53 | */ |
| 25 | 54 | class CourseLesson extends Model |
| 26 | 55 | { |
| 27 | 56 | protected $table = 'fcom_posts'; |
| 28 | 57 | |
| 29 | - protected $guarded = ['id']; | |
| 58 | + protected $guarded = [ 'id' ]; | |
| 30 | 59 | |
| 31 | 60 | protected $casts = [ |
| 32 | 61 | 'comments_count' => 'int', |
| 33 | - 'reactions_count' => 'int' | |
| 62 | + 'reactions_count' => 'int', | |
| 34 | 63 | ]; |
| 35 | 64 | |
| 36 | 65 | protected $fillable = [ |
| 37 | 66 | 'user_id', |
| @@ -51,18 +80,18 @@ | ||
| 51 | 80 | 'comments_count', |
| 52 | 81 | 'reactions_count', |
| 53 | 82 | 'meta', |
| 54 | 83 | 'priority', |
| 55 | - 'parent_id' | |
| 84 | + 'parent_id', | |
| 56 | 85 | ]; |
| 57 | 86 | |
| 58 | 87 | protected $searchable = [ |
| 59 | 88 | 'message', |
| 60 | - 'title' | |
| 89 | + 'title', | |
| 61 | 90 | ]; |
| 62 | 91 | |
| 63 | 92 | public static $publicColumns = [ |
| 64 | - 'id', 'slug', 'title', 'message_rendered', 'featured_image', 'created_at', 'privacy', 'type', 'status', 'slug', 'space_id', 'user_id', 'meta', 'content_type', 'comments_count', 'reactions_count' | |
| 93 | + 'id', 'slug', 'title', 'message_rendered', 'featured_image', 'created_at', 'privacy', 'type', 'status', 'slug', 'space_id', 'user_id', 'meta', 'content_type', 'comments_count', 'reactions_count', | |
| 65 | 94 | ]; |
| 66 | 95 | |
| 67 | 96 | protected static $type = 'course_lesson'; |
| 68 | 97 | |
| @@ -102,25 +131,55 @@ | ||
| 102 | 131 | 'media' => [ |
| 103 | 132 | 'type' => 'oembed', |
| 104 | 133 | 'url' => '', |
| 105 | 134 | 'content_type' => 'video', |
| 106 | - 'html' => '' | |
| 135 | + 'html' => '', | |
| 107 | 136 | ], |
| 108 | 137 | 'enable_comments' => 'yes', |
| 109 | 138 | 'enable_media' => 'yes', |
| 110 | - 'document_lists' => [] | |
| 139 | + 'document_lists' => [], | |
| 111 | 140 | ]; |
| 112 | 141 | } |
| 113 | 142 | |
| 114 | - protected static function generateNewSlug($newModel) | |
| 143 | + /** | |
| 144 | + * A lesson slug only has to be unique among the lessons of one course. | |
| 145 | + * | |
| 146 | + * A lesson is read at course/{courseSlug}/lessons/{lessonSlug} and | |
| 147 | + * CourseController::getLessonBySlug() looks it up with a space_id filter, so the | |
| 148 | + * same slug in two courses never competes. The type global scope keeps this off | |
| 149 | + * the other row types sharing fcom_posts. | |
| 150 | + * | |
| 151 | + * Every write path goes through here - the creating hook below for generated | |
| 152 | + * slugs, CourseAdminController::patchLesson() for author supplied ones. A | |
| 153 | + * collision gets a -{time()} suffix. | |
| 154 | + * | |
| 155 | + * @param string $slug | |
| 156 | + * @param int|null $courseId the owning course, fcom_posts.space_id | |
| 157 | + * @param int|null $ignoreId the lesson being renamed, so it can keep its own slug | |
| 158 | + * @param string $fallbackTitle used when $slug sanitizes down to nothing | |
| 159 | + * @return string | |
| 160 | + */ | |
| 161 | + public static function uniqueSlug($slug, $courseId, $ignoreId = null, $fallbackTitle = '') | |
| 115 | 162 | { |
| 116 | - $slug = Utility::slugify($newModel->title, 'lesson-' . time()); | |
| 163 | + $slug = sanitize_title($slug); | |
| 117 | 164 | |
| 118 | - // check if the slug is available for this type | |
| 119 | - $exist = self::where('slug', $slug) | |
| 120 | - ->exists(); | |
| 165 | + if (!$slug) { | |
| 166 | + $slug = Utility::slugify($fallbackTitle, 'lesson-' . time()); | |
| 167 | + } | |
| 121 | 168 | |
| 122 | - if ($exist) { | |
| 169 | + $query = self::where('slug', $slug); | |
| 170 | + | |
| 171 | + if ($courseId) { | |
| 172 | + $query->where('space_id', $courseId); | |
| 173 | + } else { | |
| 174 | + $query->whereNull('space_id'); | |
| 175 | + } | |
| 176 | + | |
| 177 | + if ($ignoreId) { | |
| 178 | + $query->where('id', '!=', $ignoreId); | |
| 179 | + } | |
| 180 | + | |
| 181 | + if ($query->exists()) { | |
| 123 | 182 | $slug = $slug . '-' . time(); |
| 124 | 183 | } |
| 125 | 184 | |
| 126 | 185 | return $slug; |
| @@ -125,8 +184,13 @@ | ||
| 125 | 184 | |
| 126 | 185 | return $slug; |
| 127 | 186 | } |
| 128 | 187 | |
| 188 | + protected static function generateNewSlug($newModel) | |
| 189 | + { | |
| 190 | + return self::uniqueSlug('', $newModel->space_id, null, $newModel->title); | |
| 191 | + } | |
| 192 | + | |
| 129 | 193 | public function topic() |
| 130 | 194 | { |
| 131 | 195 | return $this->belongsTo(CourseTopic::class, 'parent_id'); |
| 132 | 196 | } |
| @@ -158,9 +222,9 @@ | ||
| 158 | 222 | } |
| 159 | 223 | |
| 160 | 224 | public function getEnabledQuestionsAttribute() |
| 161 | 225 | { |
| 162 | - return array_filter($this->questions, function($question) { | |
| 226 | + return array_filter($this->questions, function ($question) { | |
| 163 | 227 | return Arr::isTrue($question, 'enabled'); |
| 164 | 228 | }); |
| 165 | 229 | } |
| 166 | 230 | |
| @@ -248,9 +312,9 @@ | ||
| 248 | 312 | return false; |
| 249 | 313 | } |
| 250 | 314 | |
| 251 | 315 | return (bool)Reaction::where('object_id', $this->id) |
| 252 | - ->select(['id']) | |
| 316 | + ->select([ 'id' ]) | |
| 253 | 317 | ->where('object_type', 'feed') |
| 254 | 318 | ->where('user_id', $userId) |
| 255 | 319 | ->where('type', $type) |
| 256 | 320 | ->first(); |
| @@ -267,9 +331,9 @@ | ||
| 267 | 331 | } |
| 268 | 332 | // remove all tags |
| 269 | 333 | $content = wp_strip_all_tags($content); |
| 270 | 334 | // remove new lines and tabs |
| 271 | - $content = str_replace(["\r", "\n", "\t"], ' ', $content); | |
| 335 | + $content = str_replace([ "\r", "\n", "\t" ], ' ', $content); | |
| 272 | 336 | // remove multiple spaces |
| 273 | 337 | $content = preg_replace('/\s+/', ' ', $content); |
| 274 | 338 | |
| 275 | 339 | // trim |