Николай Загумённов

Николай Загумённов

Был в сети 19 июня 2026, 19:34
Заказы не принимаю
Спасибо Паше за помощь. Открыл для себя Ext.ComponentMgr.onAvailable.

Вот такой плагин получился и не надо создавать никаких comments2.grid.js.
<?php
switch ($modx->event->name) {
    case 'OnBeforeManagerPageInit':
        
        if ($action['namespace'] == 'tickets' && $action['controller'] == 'index') {
            $modx->controller->addHtml("<script type='text/javascript'>
                Ext.ComponentMgr.onAvailable('tickets-home-tabs', function() {
                    // Тут можно переопределять все что угодно
                });
            </script>");
        }
        break;
}
Интересно? а можно ли как-то переопределить гриду в тикетс?
Как ни пробую вставлять свой файл comments2.grid.js он все время расположен выше файлов тикетс. Как я понял, чтобы переопределение работало надо, чтобы свой файл подключался ниже файлов тикетс.
Попробывал сделать так:
{$_modx->runSnippet('!TicketComments', [
    'showLog' => 1,
	'class' => 'TicketComment',
    	'loadModels' => 'UserFiles',
    	'leftJoin' => '{
        "Thumbs": {
            	"class": "UserFile",
            	"on": "Thumbs.list = TicketComment.id AND Thumbs.properties LIKE \'%w\":120,\"h\":90%\'"
        	}
    	}',
    	'select' => '{
        	"TicketComment": "*",
	        "Thumbs": "GROUP_CONCAT(Thumbs.url) as thumbs"
    	}',
    	'groupby' => 'TicketComment.id',
])}
В плейсхолдере thumbs получаю строку с картикками через запятую. Теперь осталось только сделать сниппет который будет обрабатывать эту строку.
Когда оставляю коментарий картинки не подгружаются сразу, надо обновить страницу, чтобы пути появились под комментом.
Как можно сделать чтобы они подгружались сразу?
P.S. Спасибо за GROUP_CONCAT()
Большое спасибо за такой развернутый комментарий. Я почему-то всё время хотел использовать поле parent для привязки файлов к комменту. Думал про поле list, но смущало что этих листов будет потом очень много, но сейчас понимаю, что это не так критично нежели если id пользователя совпадет с id комментария.
Про замену тэга form на div действительно забыл упомянуть, важный момент без которого работать не будет.
Выяснил, что можно использовать для этих целей UserFiles.
Надо только добавить плагин, который буде срабатывать после сохранения комментария (событие — OnCommentSave). Вообще очень много открыл для себя с этими плагинами =) В таблице компонента меняю поле parent на id созданного комментария.
<?php
// TicketCommentSave
switch ($modx->event->name) {
    case 'OnCommentSave':
        // $modx->log(1, print_r(array_keys($scriptProperties), 1));
        if ($mode == 'new') {
        	// >> Подключаем UserFiles
			$corePath = $modx->getOption('userfiles_core_path', null, $modx->getOption('core_path', null, MODX_CORE_PATH) . 'components/userfiles/');
			$UserFiles = $modx->getService('UserFiles', 'UserFiles', $corePath . 'model/userfiles/', array('core_path' => $corePath));
			// << Подключаем UserFiles
			
			$user_id = $modx->user->id; // id Авторизованного пользователя
			
			$files = $modx->getCollection('UserFile', array('parent' => $user_id));
			foreach ($files as $file) {
				$file->set('parent', $id);
				$file->save();
				if ($children = $file->getMany('Children')) {
					foreach ($children as $child) {
						$child->remove();
					}
					$file->generateThumbnails();
				}
			}
        }
    break;
}
В чанке формы добавления комментария надо добавить сам сниппет ufForm:
[[!ufForm?
	&class=`modUser`
	&list=`comment`
	&dropzone=`{"maxFilesize":2,"maxFiles":5,"acceptedFiles":".jpg, .jpeg, .gif, .png","template":"edit"}`
]]
В чанке одного комментария(tpl.Tickets.comment.one.auth) добавить:
Должна быть включена поддержка fenom в настройках pdotools.
{$_modx->runSnippet('!pdoResources', [
	'limit' 			=> 10,
	'showLog'			=> 1,
	'class' 			=> 'UserFile',
	'loadModels' 		=> 'UserFiles',
	'sortby' 			=> '{"rank":"ASC"}',
	'tpl' 				=> '@INLINE <div class="item pull-left>
						        <a href="{$url}" rel="fancybox"><img src="{$thumb}" alt=""></a>
						    </div>',
	'where' 			=> '{
						        "UserFile.parent": ' ~ $id ~ ',
						        "UserFile.createdby": '~ $createdby ~'
						        
						    }',
	'leftJoin' 			=> '{
								"Thumb": {
									"class": "UserFile",
									"on": "Thumb.parent = UserFile.id AND Thumb.properties LIKE \'%w\":120,\"h\":90%\'"
								}
						    }',
	'select' 			=> '{
								"UserFile": "url",
								"Thumb": "Thumb.url as thumb"
						    }',
])}
Будет работать только для авторизованных пользователей, так как их id мы заполняем в parent таблицы компонента UserFiles.
Я использовал компонент tvSuperSelect.
Чтобы с фронта добавлять тэги добавил плагин, который заполняет ТВ поле и таблицу компонента значениями:
<?php
// tvssTagsSave
$tvss = $modx->getService('tvsuperselect', 'tvsuperselect', $modx->getOption('core_path').'components/tvsuperselect/model/tvsuperselect/');
if (!($tvss instanceof tvSuperSelect)) {
    return '';
}

switch ($modx->event->name) {

    case 'OnDocFormSave':
        if (is_object($resource) && is_array($resource->_fields)) {
            $data = $resource->_fields;
            $resource_id = $data['id'];
            // $modx->log(1, print_r($data, 1));

            $flds = $tv_values = array();
            foreach ($data as $key => $value) {
                if ($key == 'tags')  {
                    $tv_id = 1;

                    $array = array_diff($value, array(''));
                    if (!empty($array)) {
                        $flds[] = array(
                            'resource_id' => $resource_id,
                            'tv_id' => $tv_id,
                            'data' => $array,
                        );

                        $tv_values[$tv_id] = $modx->toJSON($array);
                    }
                }
            }
             // пишем в таблицу пакета
            if (!empty($flds)) {
                // $modx->log(1, print_r($flds, 1));

                $table = $modx->getTableName('tvssOption');

                foreach ($flds as $fld) {
                    $sql = 'DELETE FROM '.$table.' WHERE `resource_id` = '.$fld['resource_id'].' AND `tv_id` = '.$fld['tv_id'];
                    $stmt = $modx->prepare($sql);
                    $stmt->execute();
                    $stmt->closeCursor();

                    $values = array();
                    foreach ($fld['data'] as $value) {
                        if (!empty($value)) {
                            $values[] = '('.$fld['resource_id'].',"'.$fld['tv_id'].'","'.addslashes($value).'")';
                        }
                    }

                    if (!empty($values)) {
                        $sql = 'INSERT INTO '.$table.' (`resource_id`,`tv_id`,`value`) VALUES '.implode(',', $values);
                        $stmt = $modx->prepare($sql);
                        $stmt->execute();
                        $stmt->closeCursor();
                    }
                }
            }

            // пишем в таблицу modTemplateVarResource
            if (!empty($tv_values)) {
                //$modx->log(1, print_r($tv_values, 1));

                foreach ($tv_values as $tv_id => $values) {
                    if (!$tv_obj = $modx->getObject('modTemplateVarResource', array(
                        'tmplvarid' => $tv_id,
                        'contentid' => $resource_id,
                    ))) {
                        $tv_obj = $modx->newObject('modTemplateVarResource');
                    }

                    $tv_obj->fromArray(array(
                        'tmplvarid' => $tv_id,
                        'contentid' => $resource_id,
                        'value' => $values,
                    ));
                    $tv_obj->save();
                    // $modx->log(1, print_r($tv_obj->toArray(), 1));

                    unset($tv_obj);
                }
            }
        }
    break;
}
Чтобы выводить во фронте поле добавил сниппет:
<?php
// tvssTagsOptions
/* @var pdoFetch $pdoFetch */
$fqn = $modx->getOption('pdoFetch.class', null, 'pdotools.pdofetch', true);
$path = $modx->getOption('pdotools_class_path', null, MODX_CORE_PATH.'components/pdotools/model/', true);
if ($pdoClass = $modx->loadClass($fqn, $path, false, true)) {
    $pdoFetch = new $pdoClass($modx, $scriptProperties);
} else {
    return false;
}
$pdoFetch->addTime('pdoTools loaded');

if (!$modx->addPackage('tvsuperselect', MODX_CORE_PATH.'components/tvsuperselect/model/')) {
    return false;
}

$tv			= $modx->getOption('tv', $scriptProperties, ''); // TV name or...
$tvid		= $modx->getOption('tvId', $scriptProperties, ''); // ... TV id
$tvInput	= $modx->getOption('tvInput', $scriptProperties, ''); // TV input name
$res		= $modx->getOption('res', $scriptProperties, 0); // Resource id
$tpl		= $modx->getOption('tpl', $scriptProperties, 'tpl.tvssTagsOptions');

$tv_where = $tv ? array( 'name' => $tv ) : '';
$tv_where = $tv_where ?: ( $tvid? array( 'id' => $tvid ) : '' );
// print_r($tv_where);

if( empty($tv_where) ) { return; }


if( $tv_obj = $modx->getObject('modTemplateVar', $tv_where) )
{
	$value = '';
	if( $res != 0 && $tv_val_obj = $modx->getObject('modTemplateVarResource', array(
			'tmplvarid'	=> $tv_obj->id,
			'contentid'	=> $res,
	))) {
		$value = $tv_val_obj->value;
	}
	$value_arr = $modx->fromJSON($value); // Массив со значениями тэгов конкретного ресурса

	// Массив, который мы передадим в процессор, там его ловить в $scriptProperties
	$processorProps = array(
	    'tv_id' => $tv_obj->id,
	);
	// Массив опций для метода runProcessor
	$otherProps = array(
	    // Здесь указываем где лежат наши процессоры
	    'processors_path' => $modx->getOption('core_path') . 'components/tvsuperselect/processors/'
	);
	// Запускаем
	$response = $modx->runProcessor('mgr/option/getoptions', $processorProps, $otherProps);
	// И возвращаем ответ от процессора

	$options_array = $modx->fromJSON($response->response); // Массив со всеми тэгами

	
	
	if (is_array($options_array['results']) || is_object($options_array['results'])) {
		foreach($options_array['results'] as $v) {
			$selected = '';
			if (is_array($value_arr) || is_object($value_arr)) {
				foreach ($value_arr as $key => $val) {
					if ($v['value'] == $val) {
						$selected = "selected=\"selected\"";
					}
				}
			}
			$options .= "<option ". $selected ." value=\"". $v['value'] ."\">";
			$options .= $v['value'];
			$options .= "</option>";
			$selected = "";
		}
	}
	
	$return = $modx->getChunk($tpl, array(
		'tv_id'			=> $tv_obj->id,
		'tv_name'		=> $tv_obj->name,
		'tv_input_name'	=> $tvInput ?: $tv_obj->name,
		'tv_value'		=> $value,
		'res_id'		=> $res,
		'options'		=> $options
	));
	
	return $return;
}
else {
	return;
}
Чанк tpl.tvssTagsOptions:
<select name="tags[]" multiple="multiple" class="js-tvSuperSelect-tags form-control" id="ticket-tags">
	[[+options]]
</select>
В чанке с формой добавления/редактирования тикетов добавил:
<link href="/js/select2-4.0.2/dist/css/select2.min.css" rel="stylesheet" />
	<script src="/js/select2-4.0.2/dist/js/select2.min.js"></script>
	<script type="text/javascript">
		$(document).ready(function(){
			$(".js-tvSuperSelect-tags").select2({
				tags: true
			});
		});
	</script>
	<div class="form-group">
		<label for="ticket-sections">Тэги</label>
		[[!tvssTagsOptions? &tv=`tags` &res=`0`]]
		// В чанке редактирования [[!tvssTagsOptions? &tv=`tags` &res=`[[+id]]`]]
		<span class="error"></span>
	</div>
Прикрутил к полю тэгов Select2, чтобы тэги было удобно заполнять и все.
Может кому пригодится =)
В mFilter2 я попробовал так сортировать по количеству коментов, не знаю на сколько так правильно делать, вроде работает.
[[!mFilter2?
	&parents=`5,6,7,8`
	&class=`Ticket`
	&element=`getTickets`
	&sortAliases=`{ "ticket":"Ticket","vote":"TicketVote" }`
	&sort=`ticket|createdon:desc,vote:desc,comms|comments:desc`
	&limit=`6`
	&tpl=`tpl.Post.row`
	&includeTVs=`item_image`
	&showLog=`1`
	&leftJoin=`{
				"Comms": {
					"class": "TicketThread",
					"on": "Ticket.id = Comms.resource"
				}
			}`
	&select=`{
				"Ticket": "*",
				"Comms": "Comms.comments as comms"
			}`
	&groupby=`Ticket.id`
]]
Путь до картинки можно менять не модификаторами во время, а при сохранении тикета, плагином на событие OnDocFormSave.
Заодно проверяет поле content на наличие картинок с таким путём и меняет 0 на id.
Может кому пригодится.
Мне пригодился этот пост. Спасибо за него.

<?php
switch ($modx->event->name) {

    case 'OnDocFormSave':
        if (is_object($resource) && is_array($resource->_fields)) {
            $data = $resource->_fields;
            $resource_id = $data['id'];
            // $modx->log(1, print_r($data, 1));

            $flds = $tv_values = array();
            foreach ($data as $key => $value) {
                if ($key == 'item_image')  {
                    $tv_id = 2; // id твшки с картинкой

                    $value = trim($value);
                    $tv_value = str_replace('assets/images/tickets/0', 'assets/images/tickets/' . $resource_id, $value);
                }
                if ($key == 'content') {
                	$content = str_replace('assets/images/tickets/0', 'assets/images/tickets/' . $resource_id, $value);
                }
            }
            if (!empty($content)) {
            	$res_obj = $modx->getObject('modResource',$resource_id);
            	$res_obj->set('content', $content);
            	$res_obj->save();
            	unset($res_obj);
            }
            // пишем в таблицу modTemplateVarResource
            if (!empty($tv_value)) {
                //$modx->log(1, print_r($tv_values, 1));

                	if (!$tv_obj = $modx->getObject('modTemplateVarResource', array(
                        'tmplvarid' => $tv_id,
                        'contentid' => $resource_id,
                    ))) {
                        $tv_obj = $modx->newObject('modTemplateVarResource');
                    }

                    $tv_obj->fromArray(array(
                        'tmplvarid' => $tv_id,
                        'contentid' => $resource_id,
                        'value' => $tv_value,
                    ));
                    $tv_obj->save();
                    // $modx->log(1, print_r($tv_obj->toArray(), 1));

                    unset($tv_obj);
            }
        }
    break;
}