PluginProbe
Import WP – CSV & XML Import Export for WordPress / 2.7.2
Import WP – CSV & XML Import Export for WordPress v2.7.2
2.15.2 2.15.1 2.15.0 2.14.24 2.14.23 2.7.0 2.7.1 2.7.10 2.7.11 2.7.12 2.7.13 2.7.14 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.9.0 All 145 releases
jc-importer / class / Common / Importer / Template / PostTemplate.php

PostTemplate.php in Import WP – CSV & XML Import Export for WordPress 2.7.2, at class/Common/Importer/Template/PostTemplate.php

733 lines 25.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ImportWP\Common\Importer\Template;
4
5 use ImportWP\Common\Attachment\Attachment;
6 use ImportWP\Common\Filesystem\Filesystem;
7 use ImportWP\Common\Ftp\Ftp;
8 use ImportWP\Common\Importer\ParsedData;
9 use ImportWP\Common\Importer\TemplateInterface;
10 use ImportWP\Common\Model\ImporterModel;
11 use ImportWP\Common\Util\Logger;
12 use ImportWP\Container;
13 use ImportWP\EventHandler;
14
15 class PostTemplate extends Template implements TemplateInterface
16 {
17 protected $name = 'Post';
18 protected $mapper = 'post';
19 protected $field_map = [
20 'ID' => 'post.ID',
21 'post_title' => 'post.post_title',
22 'post_content' => 'post.post_content',
23 'post_excerpt' => 'post.post_excerpt',
24 'post_name' => 'post.post_name',
25 'post_status' => 'post.post_status',
26 'menu_order' => 'post.menu_order',
27 'post_password' => 'post.post_password',
28 'post_date' => 'post.post_date',
29 'comment_status' => 'post.comment_status',
30 'ping_status' => 'post.ping_status',
31 'post_parent' => 'post._parent.parent',
32 ];
33
34 protected $_taxonomies = [];
35 protected $_attachments = [];
36
37 private $virtual_fields = [];
38
39 public function __construct(EventHandler $event_handler)
40 {
41 parent::__construct($event_handler);
42
43 $this->groups[] = 'post';
44 $this->groups[] = 'taxonomies';
45 $this->groups[] = 'attachments';
46
47 $this->default_template_options['post_type'] = 'post';
48
49 $this->field_options = array_merge($this->field_options, [
50 'post._parent.parent' => [$this, 'get_post_parent_options'],
51 'taxonomies.*.tax' => [$this, 'get_taxonomy_options'],
52 ]);
53 }
54
55 /**
56 * @param string $message
57 * @param int $id
58 * @param ParsedData $data
59 * @return $string
60 */
61 public function display_record_info($message, $id, $data)
62 {
63 $message = parent::display_record_info($message, $id, $data);
64
65 if (!empty($this->_taxonomies)) {
66 foreach ($this->_taxonomies as $tax => $terms) {
67 $message .= ', ' . $tax . ': (' . implode(', ', $terms) . ')';
68 }
69 }
70
71 if (!empty($this->_attachments)) {
72 $message .= ', Attachments: (' . implode(', ', $this->_attachments) . ')';
73 }
74
75 return $message;
76 }
77
78 public function register()
79 {
80 $groups = [];
81
82 // Post
83 $groups[] = $this->register_group('Post Fields', 'post', [
84 $this->register_field('ID', 'ID', [
85 'tooltip' => __('ID is only used to reference existing records', 'importwp')
86 ]),
87 $this->register_core_field('Title', 'post_title', [
88 'tooltip' => __('Title of the post.', 'importwp')
89 ]),
90 $this->register_core_field('Content', 'post_content', [
91 'tooltip' => __('Main WYSIWYG editor content of the post.', 'importwp')
92 ]),
93 $this->register_field('Excerpt', 'post_excerpt', [
94 'tooltip' => __('A custom short extract for the post.', 'importwp')
95 ]),
96 $this->register_field('Slug', 'post_name', [
97 'tooltip' => __('The slug is the user friendly and URL valid name of the post.', 'importwp')
98 ]),
99 $this->register_field('Status', 'post_status', [
100 'default' => 'publish',
101 'options' => [
102 ['value' => 'draft', 'label' => 'Draft'],
103 ['value' => 'publish', 'label' => 'Published'],
104 ['value' => 'pending', 'label' => 'Pending'],
105 ['value' => 'future', 'label' => 'Future'],
106 ['value' => 'private', 'label' => 'Private'],
107 ['value' => 'trash', 'label' => 'Trash']
108 ],
109 'tooltip' => __('Whether the post can accept comments. Accepts open or closed', 'importwp')
110 ]),
111 $this->register_group('Parent Settings', '_parent', [
112 $this->register_field('Parent', 'parent', [
113 'default' => '',
114 'options' => 'callback',
115 'tooltip' => __('Set this for the post it belongs to', 'importwp')
116 ]),
117 $this->register_field('Parent Field Type', '_parent_type', [
118 'default' => 'id',
119 'options' => [
120 ['value' => 'id', 'label' => 'ID'],
121 ['value' => 'slug', 'label' => 'Slug'],
122 ['value' => 'name', 'label' => 'Name'],
123 ['value' => 'column', 'label' => 'Reference Column'],
124 ],
125 'type' => 'select',
126 'tooltip' => __('Select how the parent field should be handled', 'importwp')
127 ]),
128 $this->register_field('Parent Reference Column', '_parent_ref', [
129 'condition' => ['_parent_type', '==', 'column'],
130 'tooltip' => __('Select the column/node that the parent field is referencing', 'importwp')
131 ])
132 ]),
133 $this->register_field('Order', 'menu_order', [
134 'tooltip' => __('The order the post should be displayed in', 'importwp')
135 ]),
136 $this->register_group('Author Settings', '_author', [
137 $this->register_field('Author', 'post_author', [
138 'tooltip' => __('The user of who added this post', 'importwp')
139 ]),
140 $this->register_field('Author Field Type', '_author_type', [
141 'default' => 'id',
142 'options' => [
143 ['value' => 'id', 'label' => 'ID'],
144 ['value' => 'login', 'label' => 'Login'],
145 ['value' => 'email', 'label' => 'Email'],
146 ],
147 'tooltip' => __('Select how the author field should be handled', 'importwp')
148 ])
149 ]),
150 $this->register_field('Password', 'post_password', [
151 'tooltip' => __('The password to access the post', 'importwp')
152 ]),
153 $this->register_field('Date', 'post_date', [
154 'tooltip' => __('The date of the post , enter in the format "YYYY-MM-DD HH:ii:ss"', 'importwp')
155 ]),
156 $this->register_field('Allow Comments', 'comment_status', [
157 'options' => [
158 ['value' => '0', 'label' => 'Disabled'],
159 ['value' => '1', 'label' => 'Enabled']
160 ],
161 'default' => '0',
162 'tooltip' => __('Whether the post can accept comments', 'importwp')
163 ]),
164 $this->register_field('Allow Pingbacks', 'ping_status', [
165 'options' => [
166 ['value' => 'closed', 'label' => 'Closed'],
167 ['value' => 'open', 'label' => 'Open']
168 ],
169 'default' => 'closed',
170 'tooltip' => __('Whether the post can accept pings', 'importwp')
171 ])
172 ]);
173
174 // Taxonomies
175 $groups[] = $this->register_taxonomy_fields();
176
177 // Attachments
178 $groups[] = $this->register_attachment_fields();
179
180 return $groups;
181 }
182
183 public function register_taxonomy_fields()
184 {
185 return $this->register_group('Taxonomies', 'taxonomies', [
186 $this->register_field('Taxonomy', 'tax', [
187 'default' => 'category',
188 'options' => 'callback',
189 'tooltip' => __('Select the type of taxonomy you are importing to.', 'importwp')
190 ]),
191 $this->register_field('Terms', 'term', [
192 'tooltip' => __('Name of the taxonomy term or terms (entered as a comma seperated list).', 'importwp')
193 ]),
194 $this->register_group('Settings', 'settings', [
195 $this->register_field('Delimiter', '_delimiter', [
196 'type' => 'text',
197 'tooltip' => 'A single character used to seperate terms when listing multiple, Leave empty to use default: ,'
198 ]),
199 $this->register_field('Enable Hierarchy', '_hierarchy', [
200 'default' => 'no',
201 'options' => [
202 ['value' => 'no', 'label' => 'No'],
203 ['value' => 'yes', 'label' => 'Yes'],
204 ],
205 'type' => 'select'
206 ]),
207 $this->register_field('Hierarchy Character', '_hierarchy_character', [
208 'default' => '>',
209 'condition' => ['_hierarchy', '==', 'yes'],
210 ]),
211 $this->register_field('Append Terms', '_append', [
212 'default' => 'no',
213 'options' => [
214 ['value' => 'no', 'label' => 'No'],
215 ['value' => 'yes', 'label' => 'Yes'],
216 ],
217 'type' => 'select'
218 ]),
219 ], ['type' => 'settings'])
220 ], ['type' => 'repeatable', 'row_base' => true]);
221 }
222
223 public function register_settings()
224 {
225 }
226
227 public function register_options()
228 {
229 return [];
230 }
231
232 /**
233 * Alter fields before they are parsed
234 *
235 * @param array $fields
236 * @return array
237 */
238 public function field_map($fields)
239 {
240 if ($this->importer->isEnabledField('post._parent') && $fields['post._parent._parent_type'] === 'column' && !empty($fields["post._parent._parent_ref"])) {
241 $fields['_iwp_ref_post_parent'] = $fields["post._parent._parent_ref"];
242 $this->virtual_fields[] = '_iwp_ref_post_parent';
243 }
244 return $fields;
245 }
246
247 /**
248 * Process data before record is importer.
249 *
250 * Alter data that is passed to the mapper.
251 *
252 * @param ParsedData $data
253 * @return ParsedData
254 */
255 public function pre_process(ParsedData $data)
256 {
257 $data = parent::pre_process($data);
258
259 $post_field_map = [];
260 foreach ($this->field_map as $field_id => $field_map_key) {
261 $post_field_map[$field_id] = $data->getValue($field_map_key);
262 }
263
264 // remove fields that have not been set
265 foreach ($post_field_map as $field_key => $field_map) {
266
267 if ($field_map === false) {
268 unset($post_field_map[$field_key]);
269 continue;
270 }
271 }
272
273 $optional_fields = [
274 'ID',
275 'post_excerpt',
276 'post_name',
277 'post_status',
278 'menu_order',
279 'post_password',
280 'post_date',
281 'comment_status',
282 'ping_status'
283 ];
284 foreach ($optional_fields as $optional_field) {
285 if (true !== $this->importer->isEnabledField('post.' . $optional_field)) {
286 unset($post_field_map[$optional_field]);
287 }
288 }
289
290 if (true !== $this->importer->isEnabledField('post._parent')) {
291 unset($post_field_map['post_parent']);
292 }
293
294 if (true !== $this->importer->isEnabledField('post._author')) {
295 unset($post_field_map['post_author']);
296 }
297
298 if (!empty($this->virtual_fields)) {
299 foreach ($this->virtual_fields as $virtual_field) {
300 $value = $data->getValue($virtual_field);
301 if (false !== $value) {
302 $post_field_map[$virtual_field] = $value;
303 }
304 }
305 }
306
307 if ($this->importer->isEnabledField('post._author')) {
308 $post_author = $data->getValue('post._author.post_author');
309 $post_author_type = $data->getValue('post._author._author_type');
310
311 $user_id = 0;
312
313 if ($post_author_type === 'id') {
314
315 $user = get_user_by('ID', $post_author);
316 if ($user) {
317 $user_id = intval($user->ID);
318 }
319 } elseif ($post_author_type === 'login') {
320
321 $user = get_user_by('login', $post_author);
322 if ($user) {
323 $user_id = intval($user->ID);
324 }
325 } elseif ($post_author_type === 'email') {
326
327 $user = get_user_by('email', $post_author);
328 if ($user) {
329 $user_id = intval($user->ID);
330 }
331 }
332
333 if ($user_id > 0) {
334 $post_field_map['post_author'] = $user_id;
335 } else {
336 $post_field_map['post_author'] = '';
337 }
338 }
339
340 // post_name is a unique field, so generate slug from title if no slug present
341 // if (!isset($post_field_map['post_name']) || empty($post_field_map['post_name'])) {
342 // $post_field_map['post_name'] = sanitize_title($post_field_map['post_title']);
343
344 // // set flag to say slug has been generated
345 // $data->add(['post_name' => 'yes'], '_generated');
346 // }
347
348 if ($this->importer->isEnabledField('post._parent') && isset($post_field_map['post_parent'])) {
349
350 $parent_id = 0;
351 $parent_field_type = $data->getValue('post._parent._parent_type');
352
353 if ($parent_field_type === 'name' || $parent_field_type === 'slug') {
354
355 // name or slug
356 $page = get_posts(array('name' => sanitize_title($post_field_map['post_parent']), 'post_type' => $this->importer->getSetting('post_type')));
357 if ($page) {
358 $parent_id = intval($page[0]->ID);
359 }
360 } elseif ($parent_field_type === 'id') {
361
362 // ID
363 $parent_id = intval($post_field_map['post_parent']);
364 } elseif ($parent_field_type === 'column') {
365
366 // reference column
367 $temp_id = $this->get_post_by_cf('post_parent', $post_field_map['post_parent']);
368 if (intval($temp_id > 0)) {
369 $parent_id = intval($temp_id);
370 }
371 }
372
373 if ($parent_id > 0) {
374 $post_field_map['post_parent'] = $parent_id;
375 }
376 }
377
378 foreach ($post_field_map as $key => $value) {
379 $post_field_map[$key] = apply_filters('iwp/template/process_field', $value, $key, $this->importer);
380 }
381
382 $data->replace($post_field_map, 'default');
383
384 return $data;
385 }
386
387 /**
388 * Find existing post/page/custom by reference field
389 *
390 * @param string $field Reference Field Name
391 * @param string $value Value to check against reference
392 *
393 * @return bool
394 */
395 public function get_post_by_cf($field, $value)
396 {
397
398 $query = new \WP_Query(array(
399 'post_type' => $this->importer->getSetting('post_type'),
400 'posts_per_page' => 1,
401 'fields' => 'ids',
402 'cache_results' => false,
403 'update_post_meta_cache' => false,
404 'meta_query' => array(
405 array(
406 'key' => '_iwp_ref_' . $field,
407 'value' => $value
408 )
409 ),
410 'post_status' => 'any'
411 ));
412 if ($query->have_posts()) {
413 return $query->posts[0];
414 }
415 return false;
416 }
417
418 /**
419 * Process data after record is importer.
420 *
421 * Use data that is returned from the mapper.
422 *
423 * @param int $post_id
424 * @param ParsedData $data
425 * @return void
426 */
427 public function post_process($post_id, ParsedData $data)
428 {
429 /**
430 * @var Filesystem $filesystem
431 */
432 $filesystem = Container::getInstance()->get('filesystem');
433
434 /**
435 * @var Ftp $ftp
436 */
437 $ftp = Container::getInstance()->get('ftp');
438
439 /**
440 * @var Attachment $attachment
441 */
442 $attachment = Container::getInstance()->get('attachment');
443
444 $this->featured_set = false;
445 $this->process_taxonomies($post_id, $data);
446 $this->process_attachments($post_id, $data, $filesystem, $ftp, $attachment);
447
448 parent::post_process($post_id, $data);
449 }
450
451 /**
452 * Process taxonomy group
453 *
454 * TODO: Possibly move this into parser?
455 *
456 * @param int $post_id
457 * @param ParsedData $data
458 * @return void
459 */
460 public function process_taxonomies($post_id, $data)
461 {
462 // reset list of taxonomies
463 $this->_taxonomies = [];
464
465 $group = 'taxonomies';
466 $taxonomes_data = $data->getData($group);
467 $total_rows = isset($taxonomes_data[$group . '._index']) ? intval($taxonomes_data[$group . '._index']) : 0;
468 $base_delimiter = apply_filters('iwp/value_delimiter', ',');
469 $base_delimiter = apply_filters('iwp/taxonomy/value_delimiter', $base_delimiter);
470
471 $processed_taxonomies = [];
472 $term_hierarchy = [];
473 $term_hierarchy_enabled = [];
474 $term_append = [];
475
476 // Pre-Process taxonomy data
477 for ($i = 0; $i < $total_rows; $i++) {
478
479 $prefix = $group . '.' . $i . '.';
480
481 $sub_rows = [$taxonomes_data];
482 if (isset($taxonomes_data[$prefix . 'row_base']) && !empty($taxonomes_data[$prefix . 'row_base'])) {
483 $sub_group_id = $group . '.' . $i;
484 $sub_rows = $data->getData($sub_group_id);
485 }
486
487 foreach ($sub_rows as $row) {
488 $tax = isset($row[$prefix . 'tax']) ? $row[$prefix . 'tax'] : null;
489 $terms = isset($row[$prefix . 'term']) ? $row[$prefix . 'term'] : null;
490
491 $term_append[$tax] = isset($row[$prefix . 'settings._append']) && $row[$prefix . 'settings._append'] == 'yes';
492
493 $delimiter = apply_filters('iwp/taxonomy=' . $tax . '/value_delimiter', $base_delimiter);
494
495 $hierarchy_enabled = isset($row[$prefix . 'settings._hierarchy']) && $row[$prefix . 'settings._hierarchy'] === 'yes' ? true : false;
496 $hierarchy_character = isset($row[$prefix . 'settings._hierarchy_character']) ? $row[$prefix . 'settings._hierarchy_character'] : null;
497
498 $delimiter = isset($row[$prefix . 'settings._delimiter']) && strlen(trim($row[$prefix . 'settings._delimiter'])) === 1 ? trim($row[$prefix . 'settings._delimiter']) : $delimiter;
499
500 if (empty($hierarchy_character)) {
501 $hierarchy_enabled = false;
502 }
503
504 if (!is_null($tax) && !is_null($terms)) {
505 $terms = explode($delimiter, $terms);
506 foreach ($terms as $term) {
507
508 $term = trim($term);
509 $permission_key = 'taxonomy.' . $tax; //taxonomy.category | taxonomy.post_tag
510
511 if ($data->permission()) {
512 $allowed = $data->permission()->validate([$permission_key => ''], $data->getMethod(), $group);
513 $is_allowed = isset($allowed[$permission_key]) ? true : false;
514
515 if (!$is_allowed || empty($term)) {
516 continue;
517 }
518 }
519
520 // handle taxonomies
521 if (!isset($processed_taxonomies[$tax])) {
522 $processed_taxonomies[$tax] = [];
523 }
524
525 $processed_taxonomies[$tax][] = $term;
526
527 // Handle hierarchy
528 if (!isset($term_hierarchy[$tax])) {
529 $term_hierarchy[$tax] = [];
530 }
531
532 if ($hierarchy_enabled) {
533 $hierarchy_parts = explode($hierarchy_character, $term);
534 if (count($hierarchy_parts) > 0) {
535 $hierarchy_parts = array_filter(array_map('trim', $hierarchy_parts));
536 $term_hierarchy[$tax][] = $hierarchy_parts;
537 $term_hierarchy_enabled[$tax] = true;
538 }
539 } else {
540 $term_hierarchy[$tax][] = [$term];
541 }
542 }
543 }
544 }
545 }
546
547 // TODO: Process term hierarchy
548 foreach ($term_hierarchy as $processed_tax => $term_hierarchy_list) {
549
550 // clear existing taxonomies
551 if (!isset($term_append[$processed_tax]) || !$term_append[$processed_tax]) {
552 wp_set_object_terms($post_id, null, $processed_tax);
553 }
554
555 if (empty($term_hierarchy_list)) {
556 continue;
557 }
558
559 foreach ($term_hierarchy_list as $hierarchy_list) {
560
561 $prev_term = isset($term_hierarchy_enabled[$processed_tax]) ? 0 : null;
562
563 foreach ($hierarchy_list as $term) {
564 if (!isset($this->_taxonomies[$processed_tax])) {
565 $this->_taxonomies[$processed_tax] = [];
566 }
567
568 $term_result = $this->create_or_get_taxonomy_term($post_id, $processed_tax, $term, $prev_term);
569 if ($term_result) {
570 $prev_term = $term_result->term_id;
571 $this->_taxonomies[$processed_tax][] = $term_result->name;
572 }
573 }
574 }
575 }
576 }
577
578 private function create_or_get_taxonomy_term($post_id, $tax, $term, $parent)
579 {
580
581 if (is_null($parent)) {
582
583 // we do not care about parent, just fetch first
584 $tmp_term = get_term_by('name', $term, $tax);
585 if ($tmp_term) {
586 wp_set_object_terms($post_id, $tmp_term->term_id, $tax, true);
587 return $tmp_term;
588 }
589 } else {
590 $terms = get_terms([
591 'taxonomy' => $tax,
592 'name' => $term,
593 'hide_empty' => false
594 ]);
595 if (!empty($terms)) {
596 foreach ($terms as $tmp_term) {
597 if (intval($tmp_term->parent) === intval($parent)) {
598
599 // attach term to post
600 wp_set_object_terms($post_id, $tmp_term->term_id, $tax, true);
601 return $tmp_term;
602 }
603 }
604 }
605 }
606
607 // add term
608 $term_id = wp_insert_term($term, $tax, ['parent' => $parent]);
609 if (!is_wp_error($term_id)) {
610 wp_set_object_terms($post_id, $term_id['term_id'], $tax, true);
611 return get_term($term_id['term_id'], $tax);
612 }
613
614 return false;
615 }
616
617 /**
618 * Process attachments group
619 *
620 * TODO: Possibly move this into parser?
621 *
622 * @param int $post_id
623 * @param ParsedData $data
624 * @param Filesystem $filesystem
625 * @param FTP $ftp
626 * @param Attachment $attachment
627 * @return void
628 */
629 public function process_attachments($post_id, $data, $filesystem, $ftp, $attachment, $group = 'attachments')
630 {
631 // reset list of attachments
632 $this->_attachments = [];
633 $attachment_ids = [];
634
635 $attachment_data = $data->getData($group);
636 $total_rows = isset($attachment_data[$group . '._index']) ? intval($attachment_data[$group . '._index']) : 0;
637 $skipped = 0;
638
639 for ($i = 0; $i < $total_rows; $i++) {
640
641 $permission_key = $group . '.' . $i; //attachment.0 | attachment.1
642 if ($data->permission()) {
643 $allowed = $data->permission()->validate([$permission_key => ''], $data->getMethod(), $group);
644 $is_allowed = isset($allowed[$permission_key]) ? true : false;
645 if (!$is_allowed) {
646 $skipped++;
647 continue;
648 }
649 }
650
651 // Process Attachments
652 $row_prefix = $group . '.' . $i . '.';
653
654 $sub_rows = [$attachment_data];
655 if (isset($attachment_data[$row_prefix . 'row_base']) && !empty($attachment_data[$row_prefix . 'row_base'])) {
656 $sub_group_id = $group . '.' . $i;
657 $sub_rows = $data->getData($sub_group_id);
658 }
659
660 foreach ($sub_rows as $row) {
661 $ids = $this->process_attachment($post_id, $row, $row_prefix, $filesystem, $ftp, $attachment);
662 $attachment_ids = array_merge($attachment_ids, $ids);
663 }
664 }
665
666 if ($skipped == $total_rows) {
667 return false;
668 }
669
670 return $attachment_ids;
671 }
672
673 /**
674 * Get list of posts
675 *
676 * @param ImporterModel $importer_model
677 *
678 * @return array
679 */
680 public function get_post_parent_options($importer_model)
681 {
682 $query = new \WP_Query(array(
683 'post_type' => $importer_model->getSetting('post_type'),
684 'posts_per_page' => -1,
685 'cache_results' => false,
686 'update_post_meta_cache' => false,
687 ));
688
689 if (is_wp_error($query)) {
690 return $query;
691 }
692
693 $result = [];
694 foreach ($query->posts as $post) {
695 $result[] = [
696 'value' => '' . $post->ID,
697 'label' => $post->post_title
698 ];
699 }
700
701 return $result;
702 }
703
704 /**
705 * Get list taxonomies
706 *
707 * @param ImporterModel $importer_model
708 *
709 * @return array
710 */
711 public function get_taxonomy_options($importer_model)
712 {
713 $taxonomies = get_object_taxonomies($importer_model->getSetting('post_type'), 'objects');
714
715 if (empty($taxonomies)) {
716 return [];
717 }
718
719 $result = [];
720 /**
721 * @var \WP_Taxonomy[] $taxonomies
722 */
723 foreach ($taxonomies as $key => $taxonomy) {
724 $result[] = [
725 'value' => '' . $key,
726 'label' => $taxonomy->label
727 ];
728 }
729
730 return $result;
731 }
732 }
733