Completed
Push — master ( 998007...d6e392 )
by Nazar
04:34
created

Sections::construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * @package   Blogs
4
 * @category  modules
5
 * @author    Nazar Mokrynskyi <[email protected]>
6
 * @copyright Copyright (c) 2011-2016, Nazar Mokrynskyi
7
 * @license   MIT License, see license.txt
8
 */
9
namespace cs\modules\Blogs;
10
use
11
	cs\Cache\Prefix as Cache_prefix,
12
	cs\Config,
13
	cs\Language,
14
	cs\Language\Prefix as Language_prefix,
15
	cs\Text,
16
	cs\User,
17
	cs\DB\Accessor,
18
	cs\Singleton;
19
20
/**
21
 * @method static $this instance($check = false)
22
 *
23
 * @todo Use CRUD trait
24
 */
25
class Sections {
26
	use
27
		Accessor,
28
		Singleton;
29
30
	/**
31
	 * @var Cache_prefix
32
	 */
33
	protected $cache;
34
35
	protected function construct () {
36
		$this->cache = new Cache_prefix('Blogs');
37
	}
38
	/**
39
	 * Returns database index
40
	 *
41
	 * @return int
42
	 */
43
	protected function cdb () {
44
		return Config::instance()->module('Blogs')->db('posts');
45
	}
46
	/**
47
	 * Get array of sections in form [<i>id</i> => <i>title</i>]
48
	 *
49
	 * @return array|false
50
	 */
51
	function get_list () {
52
		$L = Language::instance();
53
		return $this->cache->get(
54
			"sections/list/$L->clang",
55
			function () {
56
				return $this->get_list_internal(
57
					$this->get_structure()
58
				);
59
			}
60
		);
61
	}
62
	private function get_list_internal ($structure) {
63
		if (!empty($structure['sections'])) {
64
			$list = [];
65
			foreach ($structure['sections'] as $section) {
66
				$list += $this->get_list_internal($section);
67
			}
68
			return $list;
69
		} else {
70
			return [$structure['id'] => $structure['title']];
71
		}
72
	}
73
	/**
74
	 * Get array of sections structure
75
	 *
76
	 * @return array|false
77
	 */
78
	function get_structure () {
79
		$L = Language::instance();
80
		return $this->cache->get(
81
			"sections/structure/$L->clang",
82
			function () {
83
				return $this->get_structure_internal();
84
			}
85
		);
86
	}
87
	private function get_structure_internal ($parent = 0) {
88
		$structure = [
89
			'id'    => $parent,
90
			'posts' => 0
91
		];
92
		if ($parent != 0) {
93
			$structure = array_merge(
94
				$structure,
95
				$this->get($parent)
96
			);
97
		} else {
98
			$L                  = new Language_prefix('blogs_');
99
			$structure['title'] = $L->root_section;
100
			$structure['posts'] = $this->db()->qfs(
101
				[
102
					"SELECT COUNT(`s`.`id`)
103
					FROM `[prefix]blogs_posts_sections` AS `s`
104
						LEFT JOIN `[prefix]blogs_posts` AS `p`
105
					ON `s`.`id` = `p`.`id`
106
					WHERE
107
						`s`.`section`	= '%s' AND
108
						`p`.`draft`		= 0",
109
					$structure['id']
110
				]
111
			);
112
		}
113
		$sections              = $this->db()->qfa(
114
			[
115
				"SELECT
116
					`id`,
117
					`path`
118
				FROM `[prefix]blogs_sections`
119
				WHERE `parent` = '%s'",
120
				$parent
121
			]
122
		) ?: [];
123
		$structure['sections'] = [];
124
		foreach ($sections as $section) {
125
			$structure['sections'][$this->ml_process($section['path'])] = $this->get_structure_internal($section['id']);
126
		}
127
		return $structure;
128
	}
129
	/**
130
	 * Get data of specified section
131
	 *
132
	 * @param int|int[] $id
133
	 *
134
	 * @return array|false
135
	 */
136
	function get ($id) {
137
		if (is_array($id)) {
138
			foreach ($id as &$i) {
139
				$i = $this->get($i);
140
			}
141
			return $id;
142
		}
143
		$L  = Language::instance();
144
		$id = (int)$id;
145
		return $this->cache->get(
146
			"sections/$id/$L->clang",
147
			function () use ($id) {
148
				$data = $this->db()->qf(
149
					[
150
						"SELECT
151
							`id`,
152
							`title`,
153
							`path`,
154
							`parent`,
155
							(
156
								SELECT COUNT(`s`.`id`)
157
								FROM `[prefix]blogs_posts_sections` AS `s`
158
									LEFT JOIN `[prefix]blogs_posts` AS `p`
159
								ON `s`.`id` = `p`.`id`
160
								WHERE
161
									`s`.`section`	= '%1\$s' AND
162
									`p`.`draft`		= 0
163
							) AS `posts`
164
						FROM `[prefix]blogs_sections`
165
						WHERE `id` = '%1\$s'
166
						LIMIT 1",
167
						$id
168
					]
169
				);
170
				if ($data) {
171
					$data['title']     = $this->ml_process($data['title']);
172
					$data['path']      = $this->ml_process($data['path']);
173
					$data['full_path'] = [$data['path']];
174
					$parent            = $data['parent'];
175
					while ($parent != 0) {
176
						$section             = $this->get($parent);
177
						$data['full_path'][] = $section['path'];
178
						$parent              = $section['parent'];
179
					}
180
					$data['full_path'] = implode('/', array_reverse($data['full_path']));
181
				}
182
				return $data;
183
			}
184
		);
185
	}
186
	/**
187
	 * @return array[]|false
188
	 */
189
	function get_all () {
190
		$L = Language::instance();
191
		return $this->cache->get(
192
			"sections/all/$L->clang",
193
			function () {
194
				$sections = $this->db()->qfa(
195
					"SELECT *
196
					FROM `[prefix]blogs_sections`"
197
				) ?: [];
198
				foreach ($sections as &$section) {
0 ignored issues
show
Bug introduced by
The expression $sections of type integer|string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
199
					$section['id']     = (int)$section['id'];
200
					$section['parent'] = (int)$section['parent'];
201
					$section['title']  = $this->ml_process($section['title']);
202
					$section['path']   = $this->ml_process($section['path']);
203
				}
204
				return $sections;
205
			}
206
		);
207
	}
208
	/**
209
	 * Get sections ids for each section in full path
210
	 *
211
	 * @param string|string[] $path
212
	 *
213
	 * @return false|int[]
0 ignored issues
show
Documentation introduced by
Should the return type not be array|false? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
214
	 */
215
	function get_by_path ($path) {
216
		if (!is_array($path)) {
217
			$path = explode('/', $path);
218
		}
219
		$structure = $this->get_structure();
220
		$ids       = [];
221
		foreach ($path as $p) {
222
			if (!isset($structure['sections'][$p])) {
223
				break;
224
			}
225
			array_shift($path);
226
			$structure = $structure['sections'][$p];
227
			$ids[]     = $structure['id'];
228
		}
229
		return $ids ?: false;
230
	}
231
	/**
232
	 * Add new section
233
	 *
234
	 * @param int    $parent
235
	 * @param string $title
236
	 * @param string $path
237
	 *
238
	 * @return false|int Id of created section on success of <b>false</> on failure
239
	 */
240
	function add ($parent, $title, $path) {
241
		$parent = (int)$parent;
242
		$posts  = $this->db_prime()->qfa(
243
			"SELECT `id`
244
			FROM `[prefix]blogs_posts_sections`
245
			WHERE `section` = $parent"
246
		) ?: [];
247
		if ($this->db_prime()->q(
248
			"INSERT INTO `[prefix]blogs_sections`
249
				(`parent`)
250
			VALUES
251
				($parent)"
252
		)
253
		) {
254
			$Cache = $this->cache;
255
			$id    = $this->db_prime()->id();
256
			$this->db_prime()->q(
257
				"UPDATE `[prefix]blogs_posts_sections`
258
					SET `section` = $id
259
					WHERE `section` = $parent"
260
			);
261
			foreach ($posts as $post) {
262
				unset($Cache->{"posts/$post[id]"});
263
			}
264
			unset($posts, $post);
265
			$this->set($id, $parent, $title, $path);
266
			unset(
267
				$Cache->{'sections/list'},
268
				$Cache->{'sections/structure'}
269
			);
270
			return $id;
271
		}
272
		return false;
273
	}
274
	/**
275
	 * Set data of specified section
276
	 *
277
	 * @param int    $id
278
	 * @param int    $parent
279
	 * @param string $title
280
	 * @param string $path
281
	 *
282
	 * @return bool
283
	 */
284
	function set ($id, $parent, $title, $path) {
285
		$parent = (int)$parent;
286
		$path   = path($path ?: $title);
287
		$title  = xap(trim($title));
288
		$id     = (int)$id;
289
		if ($this->db_prime()->q(
290
			"UPDATE `[prefix]blogs_sections`
291
			SET
292
				`parent`	= '%s',
293
				`title`		= '%s',
294
				`path`		= '%s'
295
			WHERE `id` = '%s'
296
			LIMIT 1",
297
			$parent,
298
			$this->ml_set('Blogs/sections/title', $id, $title),
299
			$this->ml_set('Blogs/sections/path', $id, $path),
300
			$id
301
		)
302
		) {
303
			unset($this->cache->sections);
304
			return true;
305
		}
306
		return false;
307
	}
308
	/**
309
	 * Delete specified section
310
	 *
311
	 * @param int $id
312
	 *
313
	 * @return bool
314
	 */
315
	function del ($id) {
316
		$id                = (int)$id;
317
		$parent_section    = $this->db_prime()->qfs(
318
			[
319
				"SELECT `parent`
320
				FROM `[prefix]blogs_sections`
321
				WHERE `id` = '%s'
322
				LIMIT 1",
323
				$id
324
			]
325
		);
326
		$new_posts_section = $this->db_prime()->qfs(
327
			[
328
				"SELECT `id`
329
				FROM `[prefix]blogs_sections`
330
				WHERE
331
					`parent` = '%s' AND
332
					`id` != '%s'
333
				LIMIT 1",
334
				$parent_section,
335
				$id
336
			]
337
		);
338
		if ($this->db_prime()->q(
339
			[
340
				"UPDATE `[prefix]blogs_sections`
341
				SET `parent` = '%2\$s'
342
				WHERE `parent` = '%1\$s'",
343
				"UPDATE IGNORE `[prefix]blogs_posts_sections`
344
				SET `section` = '%3\$s'
345
				WHERE `section` = '%1\$s'",
346
				"DELETE FROM `[prefix]blogs_posts_sections`
347
				WHERE `section` = '%1\$s'",
348
				"DELETE FROM `[prefix]blogs_sections`
349
				WHERE `id` = '%1\$s'
350
				LIMIT 1"
351
			],
352
			$id,
353
			$parent_section,
354
			$new_posts_section ?: $parent_section
355
		)
356
		) {
357
			$this->ml_del('Blogs/sections/title', $id);
358
			$this->ml_del('Blogs/sections/path', $id);
359
			unset($this->cache->{'/'});
360
			return true;
361
		} else {
362
			return false;
363
		}
364
	}
365
	private function ml_process ($text) {
366
		return Text::instance()->process($this->cdb(), $text, true);
367
	}
368
	private function ml_set ($group, $label, $text) {
369
		return Text::instance()->set($this->cdb(), $group, $label, $text);
370
	}
371
	private function ml_del ($group, $label) {
372
		return Text::instance()->del($this->cdb(), $group, $label);
373
	}
374
}
375