Александр Туниеков

Александр Туниеков

Был в сети 9 часов назад
Заказы принимаю
Добрый день! Когда писал компонент вот на использование в хуке для FormIt не планировал, что это понадобиться. Идея была в том, что за раз нельзя бывает много писем отправить и чтобы это сделать понадобился компонент. А в FormIt одно письмо за раз отправляется. Для чего вам этот хук? Мне просто не очень понятно зачем он. Раз написали думаю нужен :), но вот зачем теряюсь в догадках :).
У меня уже спрашивали сайт агрегатор, но заказчика цена не устроила. Он думал что это просто сделать :). Вообще если сделаете компонент для агрегатора и выложите его, то думаю спрос на него будет. Можно будет за 5-10т.р продавать :). Или может дороже.
Хм… Вы сайт агрегатор планируете… Сразу не сообразил. Думал вам покупки на организации делать надо.
Вам тогда все равно надо будет либо писать компонент для организаций, либо разобраться как прикрутить мой. В принципе задачи по сложности не сильно отличаются. Именно готового функционала под сайт агрегатор нет. Нужно писать и перед этим хорошо подумать над функционалом.
Нет к сожалению нельзя. Совместимость с минишоп не нужна была. Вообще надо разбираться как сделать совместимость и возможна ли она вообще. Компонент писался на совместимость с переработанным Shopkeeper.
Ну вот я и говорю что есть только id ресурса (parent). Понятно что с помощью fenom можно в defaultBalloonContent воткнуть дополнительный сниппет и в нем все поля вытащить. Но все равно лишние запросы в базу. Метод в простыне :) лучше :) можно без доп запросов в базу обойтись :). Только вы компонент изменили и переработать немного нужно метод. Мне сейчас некогда да и особо незачем.
Посмотрел новую версию вашего компонента. Мне вот одно не понятно. Как в defaultBalloonContent попадают данные ресурса tv pagetitle и т.д. и попадают ли они вообще туда? По моему туда только parent( id ресурса) попадает. Без данных ресурса смысла в defaultBalloonContent никакого.
Ну вот стараешься вспоминаешь пишешь а в ответ «эта дикая простыня мне ни к чему» :). Я не видел вашего нового кода. Да и, думаю, не скоро увижу, но не факт что не придется снова с напильником пилить :). Я другого способа уменьшить число запросов в базу не знаю кроме как присоединять в основной запрос. Ну нужная заказчику задача сделана, а придется ли мне использовать ваш компонент и нужно ли будет его пилить поживем увидим. Успехов Вам :)
Компонент дорабатывается и это хорошо :). Единственно со времени публикации статьи было еще несколько важных правок. Только времени описать их не было. Опишу на скорую руку что помню, надеюсь, это вам поможет в совершенствовании YandexMaps2.
1. Вызов
$o['properties'][$k] = $this->modx->runSnippet('pdoResources',array(
                                                               'resources' => $row['parent'],
                                                               'tpl' => $properties[$k],
                                                               'includeTVs'=>$includeTVs,
                                                            ));
не очень хорошая идея. Как выяснилось на каждый объект в результатах поиска приходиться более десятка вызовов в базу данных. Что, конечно, тормозит работу сайта. Более разумное решение присоединить все что можно к основному запросу и потом вытащить из него нужные данные.

[[!mFilter2?
&paginator=`pdoPage`
&ajaxMode=`button`
&parents=`[[*id:is=`38`:then=`4`:else=`[[*id]]`]]`
&element=`pdoResources`
&hideContainers=`1`
&tpl=`my.tpl.mSearch2.row`
&includeTVs=`price_from,city,district,county,metro,room_facilities,hot_offer,region`
&tvPrefix=``
&class=`modResource`
&loadModels=`ms2Gallery,yandexmaps2,citystruct,easycomm`
&leftJoin=`{
	"ym2Map": {
		"class":"ym2Map",
		"alias":"ym2Map",
		"on": "ym2Map.parent = modResource.id AND ym2Map.class = 'modDocument'"
	},
	"medium1":{
	    "class":"msResourceFile",
	    "alias":"medium1",
	    "on":"medium1.resource_id = modResource.id AND medium1.parent != 0 AND medium1.path LIKE '%\/small\/%' AND medium1.active = 1 AND medium1.rank = 0"
	},
	"ecThread": {
		"class":"ecThread",
		"alias":"ecThread",
		"on": "ecThread.name = CONCAT('resource-',modResource.id)"
	},
	"CSDistrict": {
		"class":"CSDistrict",
		"alias":"CSDistrict",
		"on": "CSDistrict.id = TVdistrict.value"
	},
	"CSCounty": {
		"class":"CSCounty",
		"alias":"CSCounty",
		"on": "CSCounty.id = TVcounty.value"
	},
	"CSRegion": {
		"class":"CSRegion",
		"alias":"CSRegion",
		"on": "CSRegion.id = TVregion.value"
	}
}`
&select=`{
	"modResource":"*",
	"medium1":"medium1.url as medium,medium1.name as medium_name,medium1.alt as medium_alt",
	"ym2Map":"objects",
	"ecThread":"rating_simple",
	"CSDistrict":"CSDistrict.name as district_name",
	"CSCounty":"CSCounty.name as county_name",
	"CSRegion":"CSRegion.name as region_name"
}`
&groupby=`modResource.id`
&limit=`30`
&filters=`
tv|price_from:number,
parent:categories,
tv|city,
tv|region,
tv|district,
tv|county,
tv|metro,
tv|room_facilities,
`
&aliases=`
        tv|price_from==price,
        tv|city==city,
        tv|region==region,
        tv|district==district,
        tv|county==county,
        tv|metro==metro,
        tv|room_facilities==room_facilities,
    `
&tplFilter.outer.default=`my.tpl.mFilter2.filter.outer`
&tplFilter.row.default=`my.tpl.mFilter2.filter.checkbox`

&tplFilter.outer.price=`my.tpl.mFilter2.filter.slider`
&tplFilter.row.price=`tpl.mFilter2.filter.number`

&tplFilter.row.city=`tpl.mFilter2.filter.city`
&tplFilter.row.region=`tpl.mFilter2.filter.region`
&tplFilter.row.metro=`tpl.mFilter2.filter.metro`
&tplFilter.row.district=`tpl.mFilter2.filter.district`
&tplFilter.row.county=`tpl.mFilter2.filter.county`
&tplOuter=`tpl.mFilter2.outer3`
]]
Присоединяем
"ym2Map": {
		"class":"ym2Map",
		"alias":"ym2Map",
		"on": "ym2Map.parent = modResource.id AND ym2Map.class = 'modDocument'"
	},
и выбираем колонку с данными объектов карты «ym2Map»:«objects».
Чанк my.tpl.mSearch2.row
<div class="mse2-row">
    <!-- Блок номера -->
    <div class="block">
        <h2><a href="{$uri}">{$pagetitle}</a></h2>
        <a href="{$uri}">
        <div class="img">
            {if $hot_offer}
            <div class="fire">
                <svg width="134pt" viewBox="0.031285762786865234 0 89.66130065917969 134.0590057373047" height="134pt" xmlns="http://www.w3.org/2000/svg" data-type="shape" role="img" preserveAspectRatio="xMidYMid meet" style="stroke-width: 0px;">
                    <g>
                        <path d="M23.348 134.059C8.445 84.953 39.934 67.023 39.934 67.023 37.73 93.227 52.62 113.641 52.62 113.641c5.477-1.653 15.93-9.375 15.93-9.375 0 9.375-5.516 29.78-5.516 29.78s19.309-14.929 25.387-39.726c6.07-24.797-11.563-49.691-11.563-49.691a66.494 66.494 0 0 1-16.507 48 9.514 9.514 0 0 0 1.445-2.227c2.09-4.18 5.445-15.043 3.48-40.199C62.512 14.891 30.516 0 30.516 0c2.757 21.516-5.512 26.473-24.883 67.313-19.371 40.832 17.715 66.746 17.715 66.746zm0 0"></path>
                    </g>
                </svg>
            </div>
            {/if}
            
                <img src="{if $medium}{$medium}{else}{('assets_url' | option) ~ 'components/ms2gallery/img/web/ms2_medium.png'}{/if}" 
                alt="{$medium_alt}" title="{$medium_name}"/>
            
        </div>
        </a>
        <div class="right">
            <h2>{$pagetitle}</h2>
            {var $rating_simple_pr = $rating_simple/5*100}
            <div class="ec-stars" title="{$rating_simple}" itemscope itemtype="http://schema.org/AggregateRating">
                <span style="width: {$rating_simple_pr}%"></span>
            </div>
            {var $metroes = 'getMetroesName' | snippet : ['metro'=>$metro,'tpl'=>'getMetroesName']}
            <ul>
                {if $metroes}
                <li><span>Метро:</span>
                    
                    {$metroes}
                </li>
                {/if}
                {if $region && $region != 1}
                <li><span>Область:</span> 
                    
                        <a href="{$_modx->makeUrl($_modx->resource.id,'',['region'=>$region])}">{$region_name}</a>
                    
                </li>
                {/if}
                {if $county}
                <li><span>Округ:</span> 
                    
                        <a href="{$_modx->makeUrl($_modx->resource.id,'',['county'=>$county])}">{$county_name}</a>
                    
                </li>
                {/if}
                {if $district}
                <li><span>Район:</span>
                    
                        <a href="{$_modx->makeUrl($_modx->resource.id,'',['district'=>$district])}">{$district_name}</a>
                    
                </li>
                {/if}
            </ul>
            <div class="price">{$price_from} &#8381;/сутки</div>
            <div class="btn modal_booking_hotel" data-hotel_id="{$id}" data-hotel_pagetitle="{$pagetitle}">Бронировать</div>
        </div>
    </div>
</div>

{if count($objects) > 0}
    {foreach $objects as $k => $o}
        {var $objects.$k.properties.balloonContent = $_modx->parseChunk('tpl.balloonContent', [
            'id' => $id,
            'uri' => $uri,
            'pagetitle' => $pagetitle,
            'metroes' => $metroes,
            'district' => $district,
            'district_name' => $district_name,
            'county' => $county,
            'county_name' => $county_name,
            'price_from' => $price_from,
            'hot_offer' => $hot_offer,
            'rating_simple' => $rating_simple,
            'region' => $region,
            'region_name' => $region_name,
        ])}
        {if $price_from}
            {var $objects.$k.options.price_from = $price_from}
        {/if}
    {/foreach}
<!-- 
YandexMaps2Start{($objects | toJSON) | replace : "[" : "[ "}YandexMaps2End
-->
{/if}
{if count($objects) > 0}
    {foreach $objects as $k => $o}
        {var $objects.$k.properties.balloonContent = $_modx->parseChunk('tpl.balloonContent', [
            'id' => $id,
            'uri' => $uri,
            'pagetitle' => $pagetitle,
            'metroes' => $metroes,
            'district' => $district,
            'district_name' => $district_name,
            'county' => $county,
            'county_name' => $county_name,
            'price_from' => $price_from,
            'hot_offer' => $hot_offer,
            'rating_simple' => $rating_simple,
            'region' => $region,
            'region_name' => $region_name,
        ])}
        {if $price_from}
            {var $objects.$k.options.price_from = $price_from}
        {/if}
    {/foreach}
<!-- 
YandexMaps2Start{($objects | toJSON) | replace : "[" : "[ "}YandexMaps2End
-->
{/if}
дебаем балун и выводим объекты карты в результаты поиска

Чанк tpl.mFilter2.outer3
<div class="row msearch2" id="mse2_mfilter">
        <div id="main_image">
        {var $objects = 'YM2GetObjectsFromMF2Result' | snippet : ['results' => $results]}
        {'!YandexMaps2' | snippet : [
            'objects' => $objects,
            'mode' => 'mfilter2',
            'jquery' => '0',
        ]}
        </div>

        <div id="title_paginate">
          <div class="container">
              <svg height="512" width="512" viewBox="0 9.045000076293945 193.14199829101562 175.0540008544922" xmlns="http://www.w3.org/2000/svg" data-type="color" role="img" preserveAspectRatio="xMidYMid meet" style="">
                <g>
                    <path fill="#fc4e19" d="M185.142 24.931v15.421a18.126 18.126 0 0 0-10.234-3.153h-34.887V24.931h-8v12.268H66.617v-2.371c0-9.374-7.626-17-17-17H28.266c-9.374 0-17 7.626-17 17v3.761A18.14 18.14 0 0 0 8 40.351V9.045H0v175.054h8v-16.643h124.021v16.643h8v-16.643h45.121v16.643h8V24.931h-8zm-10.235 20.267c5.643 0 10.234 4.591 10.234 10.234v2.258H140.02V45.198h34.887zm-42.886 33.05v48.157H66.617v-2.372c0-9.374-7.626-17-17-17H28.266c-9.374 0-17 7.626-17 17v3.762A18.208 18.208 0 0 0 8 129.557v-51.31h124.021zM18.234 134.405H132.02v12.491H8v-2.258c0-5.642 4.591-10.233 10.234-10.233zm121.787 0h34.887c2.581 0 4.936.968 6.738 2.551h-41.625v-2.551zm0 12.491v-1.94h45.121v1.94h-45.121zm34.886-20.491H140.02v-8.129h45.121v11.282a18.126 18.126 0 0 0-10.234-3.153zm10.235-16.129h-45.121V91.598h45.121v18.678zm0-26.678h-45.121v-5.35h45.121v5.35zm-166.908-38.4H132.02V57.69H8v-2.258c0-5.643 4.591-10.234 10.234-10.234z" data-color="1"></path>
                </g>
              </svg>
              <div class="paginate">
                  <h1 class="zg">[[*pagetitle]]</h1>
                  [[!pdoMenu?
                    &parents=`4`
                    &resources=`-[[*id]]`
                    &level=`1`
                    &tpl=`mFilter2.tpl.menu`
                    ]]
              </div>
            </div>
        </div>

        <div class="container">
            <div id="sort">
                <div class="left">
                    <div id="mse2_sort" class="span5 col-md-5">
        				<a href="#" data-sort="price" data-dir="[[+mse2_sort:is=`price:desc`:then=`desc`]]" data-default="desc" class="sort">ПО ЦЕНЕ <span></span></a>
        				<a href="#" data-sort="rating_simple" data-dir="[[+mse2_sort:is=`rating_simple:desc`:then=`desc`]]" data-default="desc" class="sort">ПО РЕЙТИНГУ <span></span></a>
        			</div>
                </div>
                <div class="right">
                    <a class="bloch active" href="#">
                      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19 19" id="view-grid" width="100%" height="100%"><path d="M0 0h8v8H0zm11 0h8v8h-8zm0 11h8v8h-8zM0 11h8v8H0z"></path></svg>
                    </a>
                    <a class="liners" href="#">
                      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 19" id="view-list" width="100%" height="100%"><path d="M0 0h8v8H0zm11 0h14.004v8H11zm0 11h14.004v8H11zM0 11h8v8H0z"></path></svg>
                    </a>
                </div>
            </div>

            <div class="main_aside">
                <main>
                    <div id="list_of_places">
                        <div id="mse2_results">
                			[[+results]]
                		</div>
                		<div class="mse2_pagination">
                			[[!+page.nav]]
                		</div>
                    </div>
                </main>
                <aside>
                    <div id="filter">
                        <div class="top">
                            <span>ФИЛЬТР ПОИСКА</span>

                            <svg xmlns="http://www.w3.org/2000/svg" width="512pt" viewBox="0 0 512 511.9999694824219" height="512pt" data-type="shape" role="img" preserveAspectRatio="xMidYMid meet" style="stroke-width: 0px;">
                              <g>
                                  <path d="M302 90c-66.184 0-120 53.832-120 120s53.816 120 120 120 120-53.832 120-120S368.184 90 302 90zm70.605 100.605l-75 75c-2.93 2.93-6.77 4.395-10.605 4.395s-7.676-1.465-10.605-4.395l-45-45c-5.86-5.859-5.86-15.351 0-21.21s15.351-5.86 21.21 0L287 233.789l64.395-64.394c5.859-5.86 15.351-5.86 21.21 0s5.86 15.351 0 21.21zm0 0"></path>
                                  <path d="M4.395 443.973c-5.86 5.859-5.86 15.351 0 21.21l42.421 42.422c2.93 2.93 6.77 4.395 10.606 4.395s7.676-1.465 10.605-4.395l75.184-75.183-63.633-63.633zm0 0"></path>
                                  <path d="M302 0C186.219 0 92 94.203 92 210c0 41.637 12.328 80.375 33.313 113.055l-24.524 24.523 63.633 63.633 24.523-24.524C221.625 407.673 260.367 420 302 420c115.781 0 210-94.203 210-210S417.781 0 302 0zm0 360c-82.707 0-150-67.297-150-150S219.293 60 302 60c82.703 0 150 67.297 150 150s-67.297 150-150 150zm0 0"></path>
                              </g>
                            </svg>
                        </div>
                        <div class="bottom">
                            <form action="[[~[[*id]]]]" method="post" id="mse2_filters">
                    			[[+filters]]
                    			<button type="reset" class="reset" class="btn btn-default hidden">Сбросить</button>
                    		</form>
                            
                        </div>
                    </div>
                </aside>
            </div>
        </div>
</div>
{var $objects = 'YM2GetObjectsFromMF2Result' | snippet : ['results' => $results]}
        {'!YandexMaps2' | snippet : [
            'objects' => $objects,
            'mode' => 'mfilter2',
            'jquery' => '0',
        ]}
Вытаскиваем из результатов поиска и выводим на карту.
Скрипт assets/components/yandexmaps2/js/web/default.js
(function () {
    function YandexMaps2(options) {
        //
        ['map'].forEach(function (val, i, arr) {
            if (typeof(options[val]) == 'undefined' || options[val] == '') {
                console.error('[YandexMaps2] Bad config.', arr);
                return;
            }
        });

        //
        var self = this;
        self['initialized'] = false;
        self['running'] = false;

        /**
         * Инициализирует класс.
         * @returns {boolean}
         */
        self.Initialize = function (options) {
            if (!self['initialized']) {
                //
                self['config'] = {
                    map: 'ym2map',
                    center: [55.72, 37.64],
                    zoom: 10,
                };
                self['classes'] = {};
                self['selectors'] = {};

                //
                Object.keys(options).forEach(function (key) {
                    if (['selectors'].indexOf(key) !== -1) {
                        return;
                    }
                    self.config[key] = options[key];
                });
                ['selectors'].forEach(function (key) {
                    if (options[key]) {
                        Object.keys(options[key]).forEach(function (i) {
                            self.selectors[i] = options.selectors[i];
                        });
                    }
                });
            }
            self['initialized'] = true;

            return self['initialized'];
        };

        /**
         * Запускает основные действия.
         * @returns {boolean}
         */
        self.Run = function () {
            if (self['initialized'] && !self['running']) {
                ymaps.ready(self.Map['initialize']);
                // console.log('self.Run self', self);

                if (self.config['mode'] == 'mfilter2' || self.config['mode'] == 'mfilter') {
                    $(document).on('mse2_load', function (e, response) {
                        if (response['success']) {
                             //console.log('YandexMaps2 mse2_load response', response);
                            var s = response.data['results'];
                            var results1 = s.match(/(?=YandexMaps2Start)(.*?)(?=YandexMaps2End)/g);

                            //console.log(s);
                            //console.log(results1);
                            if(results1 === null){
                                results1 ="";
                            }else{
                                results1 = results1.join();
                            } 
                            var results = JSON.parse('[' + results1 + ']');
                            if (typeof(results) == 'object') {
                                var objects = [];
                                for (var a in results) {
                                    if (!results[a]['length']) {
                                        continue;
                                    }
                                    for (var b in results[a]) {
                                        objects.push(results[a][b]);
                                    }
                                }
                                self.Map.clean(function () {
                                    self.Map.addObjects(objects);
                                });
                            }
                        }
                    });
                }
            }
            self['running'] = true;

            return self['running'];
        };

        /**
         * Колбеки
         */
        self.Callbacks = {
            onLoad: function () {
                var self = this;
                // console.log('JS onLoad self', self);

                var objects = ('objects' in self['config']) ? self.config['objects'] : [];
                // console.log('JS onLoad objects', objects);

                objects.forEach(function (o) {
                    self.Map.addObject(o);
                });
                self['map'].setBounds(self.map['geoObjects'].getBounds(), {checkZoomRange:true}).then(function(){ if(self['map'].getZoom() > 10) self['map'].setZoom(10);});
            },
        };

        /**
         * Действия с картой
         */
        self.Map = {
            /**
             * Инициализация карты
             */
            initialize: function () {
                self['map'] = new ymaps.Map(self.config['map'], {
                    center: self.config['center'],
                    zoom: self.config['zoom'],
                }, {
                    searchControlProvider: 'yandex#search',
                });

                self.Callbacks.onLoad.bind(self)();

                // Отключаем зуммирование при скролле
                if (!self.config['scrollZoom']) {
                    self['map'].behaviors.disable('scrollZoom');
                }
            },

            /**
             * Добавляем на карту объекты
             */
            addObjects: function (objects) {
                objects.forEach(function (o) {
                    self.Map.addObject(o);
                });
                self['map'].setBounds(self.map['geoObjects'].getBounds(), {checkZoomRange:true}).then(function(){ if(self['map'].getZoom() > 10) self['map'].setZoom(10);});
            },

            /**
             * Добавляем на карту объект (ломаная линия, многоугольник, круг, точка)
             */
            addObject: function (arg) {
                var o;
                var type = arg['type'];
                var geometry = arg['geometry'];
                var options = $.extend({}, arg['options']);
                var properties = $.extend({}, arg['properties']);
                // console.log('properties', properties);

                //
                switch (type) {
                    case 'placemark':
                        if(options.price_from  !== undefined){
                            if(options.price_from  !== 0){
                                var iconColor = '#ef790f';
					            var iconImageHref = '/assets/templates/img/placemark.svg';
					            var placemarkLayout = ymaps.templateLayoutFactory.createClass(
					                '<div class="marker-layout"><div class="marker-layout__text">от '+
					                options.price_from +' &#8381;</div></div>');
					           options = {
            						iconLayout: placemarkLayout,
            						iconShape: {
            							type: 'Rectangle',
            							coordinates: [
            								[-40, -40], [40, -5]
            							]
            						}
            					};
                            }
                        }
                        //console.log(options);
                        o = new ymaps.Placemark(geometry, properties, options);
                        break;

                    case 'polyline':
                        o = new ymaps.Polyline(geometry, properties, options);
                        break;

                    case 'polygon':
                        o = new ymaps.Polygon(geometry, properties, options);
                        break;

                    case 'circle':
                        o = new ymaps.Circle(geometry, properties, options);
                        break;
                }

                // Добавляем на карту и в массив
                self.map['geoObjects'].add(o);

                return o;
            },

            /**
             * Очищает карту.
             */
            clean: function (callback) {
                // var tmp = [];
                // self.map['geoObjects'].each(function (o) {
                //     tmp.push(o);
                // });
                // tmp.forEach(function (o) {
                //     self.map['geoObjects'].remove(o);
                // });
                // delete tmp;
                self.map['geoObjects'].removeAll();

                callback.bind(self)();
            }
        };

        /**
         * Сообщения.
         * @type {object}
         */
        self.Message = {
            success: function (message) {
            },
            error: function (message) {
                alert(message);
            }
        };

        /**
         * Инструменты.
         * @type {object}
         */
        self.Tools = {};

        /**
         * Initialize && Run!
         */
        if (self.Initialize(options)) {
            self.Run();
        }
    }

    window.YandexMaps2 = YandexMaps2;
})();
Изменилась регулярка чтоб она работала в safary
var results1 = s.match(/(?=YandexMaps2Start)(.*?)(?=YandexMaps2End)/g);

Маштабировать и центрировать карту чтобы все объекты на нее влезали
self['map'].setBounds(self.map['geoObjects'].getBounds(), {checkZoomRange:true}).then(function(){ if(self['map'].getZoom() > 10) self['map'].setZoom(10);});
И свой тип метки на карте
if(options.price_from  !== undefined){
                            if(options.price_from  !== 0){
                                var iconColor = '#ef790f';
					            var iconImageHref = '/assets/templates/img/placemark.svg';
					            var placemarkLayout = ymaps.templateLayoutFactory.createClass(
					                '<div class="marker-layout"><div class="marker-layout__text">от '+
					                options.price_from +' &#8381;</div></div>');
					           options = {
            						iconLayout: placemarkLayout,
            						iconShape: {
            							type: 'Rectangle',
            							coordinates: [
            								[-40, -40], [40, -5]
            							]
            						}
            					};
                            }
                        }
Еще чтобы работал поиск как надо надо поправить ошибки в pdoTools
Файл core/components/pdotools/model/pdotools/pdofetch.class.php
Функция addSelects строка примерно 367:
if ($i == 0 && $this->config['setTotal']) {
                    if (is_string($fields)){
                        $fields = 'SQL_CALC_FOUND_ROWS ' . $fields;
                    }else{
                        $fields = 'SQL_CALC_FOUND_ROWS ' . implode(",",$fields);
                    }
                }
$fields = 'SQL_CALC_FOUND_ROWS '. implode(",",$fields); это чтобы в запрос не попадало SQL_CALC_FOUND_ROWS Arroy

Функция addJoins строка примерно 304
$tmp = array_merge($this->config['tvsJoin'], $tmp);
Здесь поменял порядок присоединения таблиц чтоб tv присоединялись раньше чем все остальное. Иначе mySQL ругается на CSDistrict.id = TVdistrict.value. TVdistrict присоединяется позже и типо ему оно не известно. Надо чтоб лефтджоин с TVdistrict был раньше уже сделан.

Ну вот что помню :). Надеюсь это поможет вам и остальным разработчикам :)
А вот эти строки
$modx = new modX();
$modx->initialize('mgr');
$modx->getService('error','error.modError');
точно нужны? Я делал другой плагин копируя этот и у меня из-за них возникала проблема что не находит xpdo классы компонентов. Не понятно зачем нужна вообще здесь инициализация нового объекта $modx
В файле схемы были не правильно заданы типы полей phptype. Вместо text поставил string и все заработало. На рабочем сайте обновите компонент и проверяйте как там работает.
В emailqueue.mysql.schema.xml вместо этого
<field key="body" dbtype="text" phptype="text" null="true" default=""/>
<field key="attachments" dbtype="text" phptype="text" null="true" default=""/>
сделал это
<field key="body" dbtype="text" phptype="string" null="true" default=""/>
<field key="attachments" dbtype="text" phptype="string" null="true" default=""/>
Я эти поля как в modExtra сделано так и делал.
Здравствуйте! Вот чтоб именно пустое поле получилось вообще странно :(. Пока скачиваю Опенсервер новый. Попробую поставить PHP 7.2.00 и посмотреть.
Хм. Он 3 часа обещает скачиваться. Может все таки дадите доступ к вашему MODX?
Правила приема приложений в App Store
Редакция МОЖЕТ отклонить приложения, которые:
2.11 повторяют приложения которые уже есть в App Store, особенно если их там уже много, например: фонарики, Кама Сутры, пукающие приложения.
Хорошее правило, но явно не запрет прямых аналогов.
в лучшем случае авторы зарабатывают десятки тысяч в месяц и лучше пусть один автор получает денюжку но делает безупречный продукт и имеет стимул его совершенствовать
Прямо реклама монополии :). Всю жизнь учили, что конкуренция лучше, а тут прямым текстом монополия лучше :). Я отстал от жизни? Монополия в ИТ лучше?
Мне кажется, что монополия провоцирует не совершенствовать продукт, а просто сидеть и стричь денежки :).
Если вдруг на мой компонент появиться конкурент и продажи упадут вдвое, то я, скорее всего, увеличу стоимость компонента вдвое. В расчете на то, что конкуренту тоже надо отбивать вложения, и так как, сложный компонент дорого делать, то конкурент тоже подымет стоимость.