Completed
Push — master ( 7710e6...19a091 )
by Nazar
04:14
created

Sections::get_structure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 9
rs 9.6666
cc 1
eloc 6
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\CRUD,
18
	cs\Singleton;
19
20
/**
21
 * @method static $this instance($check = false)
22
 */
23
class Sections {
24
	use
25
		CRUD,
26
		Singleton;
27
28
	protected $data_model          = [
29
		'id'     => 'int:0',
30
		'parent' => 'int:0',
31
		'title'  => 'ml:text',
32
		'path'   => 'ml:text'
33
	];
34
	protected $table               = '[prefix]blogs_sections';
35
	protected $data_model_ml_group = 'Blogs/sections';
36
	/**
37
	 * @var Cache_prefix
38
	 */
39
	protected $cache;
40
41
	protected function construct () {
42
		$this->cache = new Cache_prefix('Blogs');
43
	}
44
	/**
45
	 * Returns database index
46
	 *
47
	 * @return int
48
	 */
49
	protected function cdb () {
50
		return Config::instance()->module('Blogs')->db('posts');
51
	}
52
	/**
53
	 * Get array of sections in form [<i>id</i> => <i>title</i>]
54
	 *
55
	 * @return array|false
56
	 */
57
	function get_list () {
58
		$L = Language::instance();
59
		return $this->cache->get(
60
			"sections/list/$L->clang",
61
			function () {
62
				return $this->get_list_internal(
63
					$this->get_structure()
64
				);
65
			}
66
		);
67
	}
68
	private function get_list_internal ($structure) {
69
		if (!empty($structure['sections'])) {
70
			$list = [];
71
			foreach ($structure['sections'] as $section) {
72
				$list += $this->get_list_internal($section);
73
			}
74
			return $list;
75
		} else {
76
			return [$structure['id'] => $structure['title']];
77
		}
78
	}
79
	/**
80
	 * Get array of sections structure
81
	 *
82
	 * @return array|false
83
	 */
84
	function get_structure () {
85
		$L = Language::instance();
86
		return $this->cache->get(
87
			"sections/structure/$L->clang",
88
			function () {
89
				return $this->get_structure_internal();
90
			}
91
		);
92
	}
93
	private function get_structure_internal ($parent = 0) {
94
		$structure = [
95
			'id'    => $parent,
96
			'posts' => 0
97
		];
98
		if ($parent != 0) {
99
			$structure = array_merge(
100
				$structure,
101
				$this->get($parent)
102
			);
103
		} else {
104
			$L                  = new Language_prefix('blogs_');
105
			$structure['title'] = $L->root_section;
106
			$structure['posts'] = Posts::instance()->get_for_section_count($structure['id']);
107
		}
108
		$sections              = $this->db()->qfa(
109
			[
110
				"SELECT
111
					`id`,
112
					`path`
113
				FROM `[prefix]blogs_sections`
114
				WHERE `parent` = '%s'",
115
				$parent
116
			]
117
		) ?: [];
118
		$structure['sections'] = [];
119
		foreach ($sections as $section) {
120
			$structure['sections'][$this->ml_process($section['path'])] = $this->get_structure_internal($section['id']);
121
		}
122
		return $structure;
123
	}
124
	/**
125
	 * Get data of specified section
126
	 *
127
	 * @param int|int[] $id
128
	 *
129
	 * @return array|false
130
	 */
131
	function get ($id) {
132
		if (is_array($id)) {
133
			foreach ($id as &$i) {
134
				$i = $this->get($i);
135
			}
136
			return $id;
137
		}
138
		$L  = Language::instance();
139
		$id = (int)$id;
140
		return $this->cache->get(
141
			"sections/$id/$L->clang",
142
			function () use ($id) {
143
				$data = $this->read($id);
144
				if ($data) {
145
					$data['posts']     = Posts::instance()->get_for_section_count($id);
146
					$data['full_path'] = [$data['path']];
147
					$parent            = $data['parent'];
148
					while ($parent != 0) {
149
						$section             = $this->get($parent);
150
						$data['full_path'][] = $section['path'];
151
						$parent              = $section['parent'];
152
					}
153
					$data['full_path'] = implode('/', array_reverse($data['full_path']));
154
				}
155
				return $data;
156
			}
157
		);
158
	}
159
	/**
160
	 * @return array[]|false
161
	 */
162
	function get_all () {
163
		$L = Language::instance();
164
		return $this->cache->get(
165
			"sections/all/$L->clang",
166
			function () {
167
				$sections = $this->db()->qfa(
168
					"SELECT *
169
					FROM `[prefix]blogs_sections`"
170
				) ?: [];
171
				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...
172
					$section['id']     = (int)$section['id'];
173
					$section['parent'] = (int)$section['parent'];
174
					$section['title']  = $this->ml_process($section['title']);
175
					$section['path']   = $this->ml_process($section['path']);
176
				}
177
				return $sections;
178
			}
179
		);
180
	}
181
	/**
182
	 * Get sections ids for each section in full path
183
	 *
184
	 * @param string|string[] $path
185
	 *
186
	 * @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...
187
	 */
188
	function get_by_path ($path) {
189
		if (!is_array($path)) {
190
			$path = explode('/', $path);
191
		}
192
		$structure = $this->get_structure();
193
		$ids       = [];
194
		foreach ($path as $p) {
195
			if (!isset($structure['sections'][$p])) {
196
				break;
197
			}
198
			array_shift($path);
199
			$structure = $structure['sections'][$p];
200
			$ids[]     = $structure['id'];
201
		}
202
		return $ids ?: false;
203
	}
204
	/**
205
	 * Add new section
206
	 *
207
	 * @param int    $parent
208
	 * @param string $title
209
	 * @param string $path
210
	 *
211
	 * @return false|int Id of created section on success of <b>false</> on failure
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|string|false?

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.

Loading history...
212
	 */
213
	function add ($parent, $title, $path) {
214
		$id = $this->create([$parent, $title, $path]);
215
		if ($id) {
216
			$this->db_prime()->q(
217
				"UPDATE `[prefix]blogs_posts_sections`
218
				SET `section` = $id
219
				WHERE `section` = '%d'",
220
				$parent
221
			);
222
			unset(
223
				$this->cache->posts,
224
				$this->cache->sections
225
			);
226
		}
227
		return $id;
228
	}
229
	/**
230
	 * Set data of specified section
231
	 *
232
	 * @param int    $id
233
	 * @param int    $parent
234
	 * @param string $title
235
	 * @param string $path
236
	 *
237
	 * @return bool
238
	 */
239
	function set ($id, $parent, $title, $path) {
240
		$result = $this->update([$id, $parent, $title, $path]);
241
		if ($result) {
242
			unset($this->cache->sections);
243
		}
244
		return $result;
245
	}
246
	/**
247
	 * Delete specified section
248
	 *
249
	 * @param int $id
250
	 *
251
	 * @return bool
1 ignored issue
show
Documentation introduced by
Should the return type not be false|object?

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.

Loading history...
252
	 */
253
	function del ($id) {
254
		$id      = (int)$id;
255
		$section = $this->read($id);
256
		if (!$section || !$this->delete($id)) {
257
			return false;
258
		}
259
		$new_posts_section = $this->db_prime()->qfs(
260
			[
261
				"SELECT `id`
262
				FROM `$this->table`
263
				WHERE `parent` = '%s'
264
				LIMIT 1",
265
				$section['parent']
266
			]
267
		) ?: $section['parent'];
268
		$update            = $this->db_prime()->q(
269
			[
270
				"UPDATE `[prefix]blogs_sections`
271
				SET `parent` = '%2\$d'
272
				WHERE `parent` = '%1\$d'",
273
				"UPDATE IGNORE `[prefix]blogs_posts_sections`
274
				SET `section` = '%3\$d'
275
				WHERE `section` = '%1\$d'"
276
			],
277
			$id,
278
			$section['parent'],
279
			$new_posts_section
280
		);
281
		if ($update) {
282
			$this->cache->del('/');
283
		}
284
		return $update;
285
	}
286
	private function ml_process ($text) {
287
		return Text::instance()->process($this->cdb(), $text, true);
288
	}
289
}
290