1: <?php
2:
3: 4: 5: 6: 7: 8:
9:
10: namespace rsanchez\Deep\Plugin;
11:
12: use rsanchez\Deep\Deep;
13: use rsanchez\Deep\Model\Entry;
14: use rsanchez\Deep\Model\Category;
15: use rsanchez\Deep\Collection\AbstractTitleCollection;
16: use rsanchez\Deep\Collection\RelationshipCollection;
17: use rsanchez\Deep\Collection\FilterableInterface;
18: use rsanchez\Deep\Collection\CategoryCollection;
19: use Illuminate\Support\Collection;
20: use DateTime;
21: use Closure;
22:
23: 24: 25:
26: abstract class BasePlugin
27: {
28: 29: 30:
31: protected $app;
32:
33: 34: 35:
36: protected $paginator;
37:
38: 39: 40: 41:
42: public function __construct()
43: {
44: $this->app = Deep::getInstance();
45:
46: $this->app->bootEE(ee());
47:
48: ee()->load->library(array('pagination', 'typography'));
49: }
50:
51: 52: 53: 54: 55:
56: protected function getEntriesDefaultParameters()
57: {
58: return array(
59: 'dynamic' => 'yes',
60: 'limit' => '100',
61: 'orderby' => 'entry_date',
62: 'paginate' => 'bottom',
63: 'show_future_entries' => 'no',
64: 'show_expired' => 'no',
65: 'sort' => 'desc',
66: 'status' => 'open',
67: );
68: }
69:
70: 71: 72: 73: 74:
75: protected function getCategoriesDefaultParameters()
76: {
77: return array(
78: 'show_empty' => 'yes',
79: 'show_future_entries' => 'no',
80: 'show_expired' => 'no',
81: 'restrict_channel' => 'yes',
82: 'style' => 'nested',
83: 'id' => 'nav_categories',
84: 'orderby' => 'categories.group_id|categories.parent_id|categories.cat_order',
85: );
86: }
87:
88: 89: 90: 91: 92: 93:
94: protected function getEntries(Closure $callback = null)
95: {
96: foreach ($this->getEntriesDefaultParameters() as $key => $value) {
97: if (! isset(ee()->TMPL->tagparams[$key])) {
98: ee()->TMPL->tagparams[$key] = $value;
99: }
100: }
101:
102: $disabled = empty(ee()->TMPL->tagparams['disable']) ? array() : explode('|', ee()->TMPL->tagparams['disable']);
103:
104: $this->paginator = ee()->pagination->create();
105:
106: $limit = ee()->TMPL->fetch_param('limit');
107:
108: ee()->TMPL->tagdata = $this->paginator->prepare(ee()->TMPL->tagdata);
109:
110: $customFieldsEnabled = ! in_array('custom_fields', $disabled);
111: $memberDataEnabled = ! in_array('members', $disabled);
112: $paginationEnabled = ! in_array('pagination', $disabled);
113: $categoriesEnabled = ! in_array('categories', $disabled);
114: $categoryFieldsEnabled = $categoriesEnabled && ! in_array('category_fields', $disabled);
115:
116: if ($limit && $paginationEnabled) {
117: unset(ee()->TMPL->tagparams['offset']);
118: } else {
119: $this->paginator->paginate = false;
120: }
121:
122: $uri = ee()->uri->page_query_string ?: ee()->uri->query_string;
123:
124: if (preg_match('#^((.*?)/)?P\d+/?$#', $uri, $match)) {
125: $uri = $match[2];
126: }
127:
128: if (! $uri && ee()->TMPL->fetch_param('require_entry') === 'yes') {
129: return ee()->TMPL->no_results();
130: }
131:
132: ee()->TMPL->tagparams['category_request'] = false;
133:
134: $singleEntry = false;
135:
136: $relatedCategoriesMode = ee()->TMPL->fetch_param('related_categories_mode') === 'yes';
137:
138: $dynamic = ee()->TMPL->fetch_param('dynamic', 'yes') === 'yes';
139:
140: $query = $this->app->make($customFieldsEnabled ? 'Entry' : 'Title')->newQuery();
141:
142: if ($uri && ($dynamic || $relatedCategoriesMode)) {
143: $segments = explode('/', $uri);
144: $lastSegment = array_pop($segments);
145: $penultimateSegment = array_pop($segments);
146:
147: if (preg_match('#(^|/)(\d{4})/(\d{2})(/(\d{2}))?/?$#', $uri, $match)) {
148: ee()->TMPL->tagparams['year'] = $match[2];
149: ee()->TMPL->tagparams['month'] = $match[3];
150:
151: if (isset($match[5])) {
152: ee()->TMPL->tagparams['day'] = $match[5];
153: }
154: } elseif (is_numeric($lastSegment)) {
155: if ($relatedCategoriesMode) {
156: $singleEntry = true;
157: $query->relatedCategories($lastSegment);
158: ee()->TMPL->tagparams['dynamic'] = 'no';
159: } else {
160: ee()->TMPL->tagparams['entry_id'] = $lastSegment;
161: }
162: } elseif (
163: ee()->config->item('use_category_name') === 'y' &&
164: $penultimateSegment === ee()->config->item('reserved_category_word')
165: ) {
166: ee()->TMPL->tagparams['category_name'] = $lastSegment;
167:
168: ee()->TMPL->tagparams['category_request'] = true;
169: } elseif (
170: ee()->config->item('use_category_name') !== 'y' &&
171: preg_match('#(^|/)C(\d+)#', $uri, $match)
172: ) {
173: ee()->TMPL->tagparams['category'] = $match[2];
174:
175: ee()->TMPL->tagparams['category_request'] = true;
176: } else {
177: if ($relatedCategoriesMode) {
178: $singleEntry = true;
179: $query->relatedCategoriesUrlTitle($lastSegment);
180: ee()->TMPL->tagparams['dynamic'] = 'no';
181: } else {
182: ee()->TMPL->tagparams['url_title'] = $lastSegment;
183: }
184: }
185: }
186:
187: if ($relatedCategoriesMode && ! $singleEntry) {
188: return ee()->TMPL->no_results();
189: }
190:
191: $query->tagparams(ee()->TMPL->tagparams);
192:
193: if ($categoriesEnabled) {
194: $query->withCategories(function ($query) use ($categoryFieldsEnabled) {
195: if ($categoryFieldsEnabled) {
196: $query->withFields();
197: }
198:
199: return $query->orderBy('categories.group_id')
200: ->orderBy('categories.parent_id')
201: ->orderBy('categories.cat_order');
202: });
203: }
204:
205: if ($memberDataEnabled) {
206: $query->withAuthorFields();
207: }
208:
209: $connection = $query->getQuery()->getConnection();
210: $tablePrefix = $connection->getTablePrefix();
211:
212: if (strpos(ee()->TMPL->tagdata, 'comment_subscriber_total') !== false) {
213: $subquery = "(select count(*) "
214: ."from `{$tablePrefix}comment_subscriptions` "
215: ."where `{$tablePrefix}comment_subscriptions`.`entry_id` "
216: ."= `{$tablePrefix}`channel_titles`.`entry_id`) "
217: ."as `comment_subscriber_total`";
218:
219: $query->addSelect($connection->raw($subquery));
220: }
221:
222: $prefix = ee()->TMPL->fetch_param('var_prefix') ? ee()->TMPL->fetch_param('var_prefix').':' : '';
223:
224: if (strpos(ee()->TMPL->tagdata, '{'.$prefix.'parents') !== false) {
225: $query->withParents();
226: }
227:
228: if (strpos(ee()->TMPL->tagdata, '{'.$prefix.'siblings') !== false) {
229: $query->withSiblings();
230: }
231:
232: if (is_callable($callback)) {
233: $callback($query);
234: }
235:
236: ee()->TMPL->tagparams['absolute_results'] = $limit;
237:
238: if ($this->paginator->paginate) {
239: ee()->TMPL->tagparams['absolute_results'] = $query->getQuery()->getPaginationCount();
240:
241: $this->paginator->build(ee()->TMPL->tagparams['absolute_results'], $limit);
242:
243: if ($this->paginator->offset) {
244: $query->skip($this->paginator->offset);
245:
246: ee()->TMPL->tagparams['offset'] = $this->paginator->offset;
247: }
248: }
249:
250: return $query->get();
251: }
252:
253: 254: 255: 256: 257: 258:
259: public function parseEntries(Closure $callback = null)
260: {
261: $entries = $this->getEntries($callback);
262:
263: $output = $this->parseEntryCollection(
264: $entries,
265: ee()->TMPL->tagdata,
266: ee()->TMPL->tagparams,
267: ee()->TMPL->var_pair,
268: ee()->TMPL->var_single
269: );
270:
271: if ($this->paginator->paginate) {
272: $output = $this->paginator->render($output);
273: }
274:
275: return $output;
276: }
277:
278: 279: 280: 281: 282: 283: 284: 285: 286: 287:
288: protected function parseEntryCollection(
289: AbstractTitleCollection $entries,
290: $tagdata,
291: array $params = array(),
292: array $varPair = array(),
293: array $varSingle = array()
294: ) {
295: $disabled = empty($params['disable']) ? array() : explode('|', $disable);
296:
297: $offset = isset($params['offset']) ? $params['offset'] : 0;
298:
299: $absoluteResults = isset($params['absolute_results']) ? $params['absolute_results'] : $entries->count();
300:
301: $customFieldsEnabled = ! in_array('custom_fields', $disabled);
302: $memberDataEnabled = ! in_array('member_data', $disabled);
303: $categoriesEnabled = ! in_array('categories', $disabled);
304: $categoryFieldsEnabled = $categoriesEnabled && ! in_array('category_fields', $disabled);
305:
306: ee()->load->library('typography');
307:
308: if (! empty($params['var_prefix'])) {
309: $prefix = rtrim($params['var_prefix'], ':').':';
310: $prefixLength = strlen($prefix);
311: } else {
312: $prefix = '';
313: $prefixLength = 0;
314: }
315:
316: $singleTags = array();
317: $pairTags = array();
318:
319: foreach (array_keys($varSingle) as $tag) {
320: $spacePosition = strpos($tag, ' ');
321:
322: if ($spacePosition !== false) {
323: $name = substr($tag, 0, $spacePosition);
324: $tagparams = ee()->functions->assign_parameters(substr($tag, $spacePosition));
325: } elseif (preg_match('#^([a-z_]+)=([\042\047]?)?(.*?)\\2$#', $tag, $match)) {
326: $name = $match[1];
327: $tagparams = $match[3] ? array($match[3]) : array('');
328: } else {
329: $name = $tag;
330: $tagparams = array();
331: }
332:
333: if ($prefix && strncmp($name, $prefix, $prefixLength) !== 0) {
334: continue;
335: }
336:
337: $singleTags[] = (object) array(
338: 'name' => $prefix ? substr($name, $prefixLength) : $name,
339: 'key' => $tag,
340: 'params' => $tagparams,
341: 'tagdata' => '',
342: );
343: }
344:
345: foreach ($varPair as $tag => $tagparams) {
346: $spacePosition = strpos($tag, ' ');
347:
348: $name = $spacePosition === false ? $tag : substr($tag, 0, $spacePosition);
349:
350: if ($prefix && strncmp($name, $prefix, $prefixLength) !== 0) {
351: continue;
352: }
353:
354: preg_match_all('#{('.preg_quote($tag).'}(.*?){/'.preg_quote($name).')}#s', $tagdata, $matches);
355:
356: foreach ($matches[1] as $i => $key) {
357: $pairTags[] = (object) array(
358: 'name' => $prefix ? substr($name, $prefixLength) : $name,
359: 'key' => $key,
360: 'params' => $tagparams ?: array(),
361: 'tagdata' => $matches[2][$i],
362: );
363: }
364: }
365:
366: $variables = array();
367:
368: foreach ($entries as $i => $entry) {
369: $row = array(
370: $prefix.'absolute_count' => $offset + $i + 1,
371: $prefix.'absolute_results' => $absoluteResults,
372: $prefix.'category_request' => isset($params['category_request']) ? $params['category_request'] : false,
373: $prefix.'not_category_request' => isset($params['category_request']) ? ! $params['category_request'] : true,
374: $prefix.'channel' => $entry->channel->channel_name,
375: $prefix.'channel_short_name' => $entry->channel->channel_name,
376: $prefix.'comment_auto_path' => $entry->channel->comment_url,
377: $prefix.'comment_entry_id_auto_path' => $entry->channel->comment_url.'/'.$entry->entry_id,
378: $prefix.'comment_url_title_auto_path' => $entry->channel->comment_url.'/'.$entry->url_title,
379: $prefix.'entry_site_id' => $entry->site_id,
380: $prefix.'forum_topic' => (int) (bool) $entry->forum_topic_id,
381: $prefix.'not_forum_topic' => (int) ! $entry->forum_topic_id,
382: $prefix.'page_uri' => $entry->page_uri,
383: $prefix.'page_url' => ee()->functions->create_url($entry->page_uri),
384: $prefix.'entry_id_path' => array($entry->entry_id, array('path_variable' => true)),
385: $prefix.'permalink' => array($entry->entry_id, array('path_variable' => true)),
386: $prefix.'title_permalink' => array($entry->url_title, array('path_variable' => true)),
387: $prefix.'url_title_path' => array($entry->url_title, array('path_variable' => true)),
388: $prefix.'profile_path' => array($entry->author_id, array('path_variable' => true)),
389: );
390:
391: foreach ($pairTags as $tag) {
392: if ($tag->name === 'parents' || $tag->name === 'siblings') {
393: $value = $entry->{$tag->name};
394:
395: $tag->params['var_prefix'] = $prefix.$tag->name;
396: $tag->vars = ee()->functions->assign_variables($tag->tagdata);
397:
398: if (! empty($tag->params['field'])) {
399: $fieldRepository = $this->app->make('FieldRepository');
400:
401: $fieldIds = array();
402:
403: foreach (explode('|', $tag->params['field']) as $fieldName) {
404: if ($fieldRepository->hasField($fieldName)) {
405: $fieldIds[] = $fieldRepository->getFieldId($fieldName);
406: }
407: }
408:
409: if ($fieldIds) {
410: $tag->params['field_id'] = implode('|', $fieldIds);
411: }
412:
413: unset($tag->params['field']);
414: }
415:
416: $row[$tag->key] = $this->parseEntryCollection(
417: $value($tag->params),
418: $tag->tagdata,
419: $tag->params,
420: $tag->vars['var_pair'],
421: $tag->vars['var_single']
422: );
423: } elseif ($categoriesEnabled && $tag->name === 'categories') {
424:
425: $categories = array();
426:
427: foreach ($entry->categories->tagparams($tag->params) as $categoryModel) {
428: $category = $categoryModel->toArray();
429:
430: unset(
431: $category['cat_id'],
432: $category['cat_name'],
433: $category['cat_description'],
434: $category['cat_image'],
435: $category['cat_url_title'],
436: $category['group_id']
437: );
438:
439: $categoryUri = ee()->config->item('use_category_name') === 'y'
440: ? '/'.ee()->config->item('reserved_category_word').'/'.$categoryModel->cat_url_title
441: : '/C'.$categoryModel->cat_id;
442:
443: $regex = '#'.preg_quote($categoryUri).'(\/|\/P\d+\/?)?$#';
444:
445: $category['active'] = (bool) preg_match($regex, ee()->uri->uri_string());
446: $category['category_description'] = $categoryModel->cat_description;
447: $category['category_group'] = $categoryModel->group_id;
448: $category['category_id'] = $categoryModel->cat_id;
449: $category['category_image'] = $categoryModel->cat_image;
450: $category['category_name'] = $categoryModel->cat_name;
451: $category['category_url_title'] = $categoryModel->cat_url_title;
452: $category['path'] = array($categoryUri, array('path_variable' => true));
453:
454: array_push($categories, $category);
455: }
456:
457:
458: $row[$tag->key] = $categories ? ee()->typography->parse_file_paths(ee()->TMPL->parse_variables($tag->tagdata, $categories)) : '';
459:
460: } elseif ($customFieldsEnabled && $entry->channel->fields->hasField($tag->name)) {
461:
462: $row[$tag->key] = '';
463:
464: $value = $entry->{$tag->name};
465:
466: if ($value instanceof AbstractTitleCollection) {
467:
468: if ($value instanceof RelationshipCollection) {
469: $tag->params['var_prefix'] = $tag->name;
470: }
471:
472: $tag->vars = ee()->functions->assign_variables($tag->tagdata);
473:
474: $value = $this->parseEntryCollection(
475: $value($tag->params),
476: $tag->tagdata,
477: $tag->params,
478: $tag->vars['var_pair'],
479: $tag->vars['var_single']
480: );
481: } elseif ($value instanceof FilterableInterface) {
482: $value = $value($tag->params)->toArray();
483: } elseif (is_object($value) && method_exists($value, 'toArray')) {
484: $value = $value->toArray();
485: } elseif ($value) {
486: $value = (string) $value;
487: }
488:
489: if ($value) {
490: if (is_array($value)) {
491: $row[$tag->key] = ee()->TMPL->parse_variables($tag->tagdata, $value);
492:
493: if (isset($tag->params['backspace'])) {
494: $row[$tag->key] = substr($row[$tag->key], 0, -$tag->params['backspace']);
495: }
496: } else {
497: $row[$tag->key] = $value;
498: }
499: }
500: }
501: }
502:
503: foreach ($singleTags as $tag) {
504: if ($customFieldsEnabled && $entry->channel->fields->hasField($tag->name)) {
505: $row[$tag->key] = (string) $entry->{$tag->name};
506: }
507:
508: if ($entry->{$tag->name} instanceof DateTime) {
509: $format = isset($tag->params['format']) ? preg_replace('#%([a-zA-Z])#', '\\1', $tag->params['format']) : 'U';
510:
511: $row[$tag->key] = $entry->{$tag->name}->format($format);
512: }
513: }
514:
515: foreach ($entry->getOriginal() as $key => $value) {
516: $row[$prefix.$key] = $value;
517: }
518:
519: foreach ($entry->channel->toArray() as $key => $value) {
520: $row[$prefix.$key] = $value;
521: }
522:
523: $row[$prefix.'allow_comments'] = (int) ($entry->allow_comments === 'y');
524: $row[$prefix.'sticky'] = (int) ($entry->sticky === 'y');
525:
526: if ($memberDataEnabled) {
527: foreach ($entry->author->toArray() as $key => $value) {
528: $row[$prefix.$key] = $value;
529: }
530:
531: $row[$prefix.'author'] = $entry->author->screen_name ?: $entry->author->username;
532: $row[$prefix.'avatar_url'] = $entry->author->avatar_filename ? ee()->config->item('avatar_url').$entry->author->avatar_filename : '';
533: $row[$prefix.'avatar_image_height'] = $entry->author->avatar_height;
534: $row[$prefix.'avatar_image_width'] = $entry->author->avatar_width;
535: $row[$prefix.'avatar'] = (int) (bool) $entry->author->avatar_filename;
536: $row[$prefix.'photo_url'] = $entry->author->photo_filename ? ee()->config->item('photo_url').$entry->author->photo_filename : '';
537: $row[$prefix.'photo_image_height'] = $entry->author->photo_height;
538: $row[$prefix.'photo_image_width'] = $entry->author->photo_width;
539: $row[$prefix.'photo'] = (int) (bool) $entry->author->photo_filename;
540: $row[$prefix.'signature_image_url'] = $entry->author->sig_img_filename ? ee()->config->item('sig_img_url').$entry->author->sig_img_filename : '';
541: $row[$prefix.'signature_image_height'] = $entry->author->sig_img_height;
542: $row[$prefix.'signature_image_width'] = $entry->author->sig_img_width;
543: $row[$prefix.'signature_image'] = (int) (bool) $entry->author->sig_img_filename;
544: $row[$prefix.'url_or_email'] = $entry->author->url ?: $entry->author->email;
545: $row[$prefix.'url_or_email_as_author'] = '<a href="'.($entry->author->url ?: 'mailto:'.$entry->author->email).'">'.$row[$prefix.'author'].'</a>';
546: $row[$prefix.'url_or_email_as_link'] = '<a href="'.($entry->author->url ?: 'mailto:'.$entry->author->email).'">'.$row[$prefix.'url_or_email'].'</a>';
547: }
548:
549: $variables[] = $row;
550: }
551:
552: if (preg_match('#{if '.preg_quote($prefix).'no_results}(.*?){/if}#s', $tagdata, $match)) {
553: $tagdata = str_replace($match[0], '', $tagdata);
554: ee()->TMPL->no_results = $match[1];
555: }
556:
557: if (! $variables) {
558: return ee()->TMPL->no_results();
559: }
560:
561: $output = ee()->TMPL->parse_variables($tagdata, $variables);
562:
563: if (! empty($params['backspace'])) {
564: $output = substr($output, 0, -$params['backspace']);
565: }
566:
567: return $output;
568: }
569:
570: 571: 572: 573: 574: 575:
576: protected function parseCategories(Closure $callback = null)
577: {
578: foreach ($this->getCategoriesDefaultParameters() as $key => $value) {
579: if (! isset(ee()->TMPL->tagparams[$key])) {
580: ee()->TMPL->tagparams[$key] = $value;
581: }
582: }
583:
584: $query = $this->app->make('Category')->nested()->tagparams(ee()->TMPL->tagparams);
585:
586: $customFieldsEnabled = ee()->TMPL->fetch_param('disable') !== 'category_fields';
587:
588: if ($customFieldsEnabled) {
589: $query->withFields();
590: }
591:
592: if (is_callable($callback)) {
593: $callback($query);
594: }
595:
596: $categories = $query->get();
597:
598: $prefix = ee()->TMPL->fetch_param('var_prefix') ? ee()->TMPL->fetch_param('var_prefix').':' : '';
599:
600: if (preg_match('#{if '.preg_quote($prefix).'no_results}(.*?){/if}#s', ee()->TMPL->tagdata, $match)) {
601: ee()->TMPL->tagdata = str_replace($match[0], '', ee()->TMPL->tagdata);
602: ee()->TMPL->no_results = $match[1];
603: }
604:
605: if ($categories->isEmpty()) {
606: return ee()->TMPL->no_results();
607: }
608:
609: if (ee()->TMPL->fetch_param('style') === 'nested') {
610: $output = '<ul id="'.ee()->TMPL->fetch_param('id', 'nav_categories').'" class="'.ee()->TMPL->fetch_param('class', 'nav_categories').'">';
611:
612: foreach ($categories as $category) {
613: $output .= $this->categoryToList($category, ee()->TMPL->tagdata, $customFieldsEnabled, $prefix);
614: }
615:
616: $output .= '</ul>';
617: } else {
618: $variables = array();
619:
620: $this->categoryCollectionToVariables($categories, $variables, $customFieldsEnabled, $prefix);
621:
622: $output = ee()->TMPL->parse_variables(ee()->TMPL->tagdata, $variables);
623:
624: if ($backspace = ee()->TMPL->fetch_param('backspace')) {
625: $output = substr($output, 0, -$backspace);
626: }
627: }
628:
629: return $output;
630:
631: }
632:
633: 634: 635: 636: 637: 638: 639: 640: 641:
642: protected function categoryCollectionToVariables(CategoryCollection $categories, array &$variables, $customFieldsEnabled = false, $prefix = '')
643: {
644: foreach ($categories as $category) {
645: $variables[] = $this->categoryToVariables($category, $customFieldsEnabled, $prefix);
646:
647: if ($category->hasChildren()) {
648: $this->categoryCollectionToVariables($category->children, $variables, $customFieldsEnabled, $prefix);
649: }
650: }
651: }
652:
653: 654: 655: 656: 657: 658: 659: 660:
661: protected function categoryToVariables(Category $category, $customFieldsEnabled = false, $prefix = '')
662: {
663: $categoryUri = ee()->config->item('use_category_name') === 'y'
664: ? '/'.ee()->config->item('reserved_category_word').'/'.$category->cat_url_title
665: : '/C'.$category->cat_id;
666:
667: $regex = '#'.preg_quote($categoryUri).'(\/|\/P\d+\/?)?$#';
668:
669: $row = array(
670: $prefix.'active' => (bool) preg_match($regex, ee()->uri->uri_string()),
671: $prefix.'category_description' => $category->cat_description,
672: $prefix.'category_id' => $category->cat_id,
673: $prefix.'parent_id' => $category->parent_id,
674: $prefix.'category_image' => $category->cat_image,
675: $prefix.'category_name' => $category->cat_name,
676: $prefix.'category_url_title' => $category->cat_url_title,
677: $prefix.'path' => array($categoryUri, array('path_variable' => true)),
678: );
679:
680: if ($customFieldsEnabled) {
681: foreach ($category->getFields() as $field) {
682: $row[$prefix.$field->field_name] = $category->{$field->field_name};
683: }
684: }
685:
686: return $row;
687: }
688:
689: 690: 691: 692: 693: 694: 695: 696: 697:
698: protected function categoryToList(Category $category, $tagdata, $customFieldsEnabled = false, $prefix = '')
699: {
700: $output = '<li>';
701:
702: $output .= ee()->TMPL->parse_variables_row($tagdata, $this->categoryToVariables($category, $customFieldsEnabled, $prefix));
703:
704: if ($category->hasChildren()) {
705: $output .= ' <ul>';
706:
707: foreach ($category->children as $child) {
708: $output .= $this->categoryToList($child, $tagdata, $customFieldsEnabled, $prefix);
709: }
710:
711: $output .= '</ul>';
712: }
713:
714: $output .= '</li>';
715:
716: return $output;
717: }
718: }
719: