Виталий Серый

Виталий Серый

С нами с 17 января 2013; Место в рейтинге пользователей: #107
Василий Наумкин
19 ноября 2015, 10:23
10
+7
My quick example:
<?php
$tplWrapper = '@INLINE <ul>{{+output}}</ul>';
$tplYear = '@INLINE <li>{{+year}}<sup>({{+count}})</sup><ul>{{+resources}}</ul></li>';
$tplMonth = '@INLINE <li>{{+month}}<sup>({{+count}})</sup><ul>{{+resources}}</ul></li>';
$tpl = '@INLINE <li><a href="{{+uri}}">{{+pagetitle}}</a></li>';

$pdo = $modx->getService('pdoFetch');

$resources = $pdo->getCollection(
	'modResource',
	array('published' => true, 'deleted' => false),
	array('parents' => 0, 'sortby' => 'createdon', 'sortdir' => 'DESC')
);
$tree = array();
foreach ($resources as $resource) {
	$year = date('Y', $resource['createdon']);
	$month = date('m', $resource['createdon']);
	$tree[$year][$month][] = $resource;
}

$output = '';
foreach ($tree as $year => $months) {
	$tmp1 = '';
	$count = 0;
	foreach ($months as $month => $resources) {
		$tmp2 = '';
		foreach ($resources as $resource) {
			$tmp2 .= $pdo->getChunk($tpl, $resource);
			$count++;
		}
		$tmp1 .=  $pdo->getChunk($tplMonth, array(
			'month' => $month,
			'count' => count($resources),
			'resources' => $tmp2,
		));
	}
	$output .=  $pdo->getChunk($tplYear, array(
		'year' => $year,
		'count' => $count,
		'resources' => $tmp1,
	));
}

return $pdo->getChunk($tplWrapper, array('output' => $output));

Result:
Илья Уткин
05 сентября 2015, 10:31
1
+2
Я делаю так:
alt="[[+pagetitle:replace=` "== «`:replace:`"==»]]"
А еще можно использовать Jevix —вы заметили, как здесь все ваши кавычки были заменены? Даже внутренние.
Василий Наумкин
07 августа 2015, 12:53
2
+6
Проверил на скорую руку, вроде работает:
1. Берем файл для Smarty.
2. Загружаем его на сервер как /assets/components/ace/ace/mode-smarty.js
3. В файле /assets/components/ace/modx.texteditor.js на 7й строке меняем mode c text на smarty.
4. Хорошенько чистим кэш браузера

Результат:
Василий Наумкин
03 апреля 2015, 04:41
1
+4
как можно запустить скрипт от рута из под PHP, либо дать пользователю все необходимые права на выполнение скрипта
Сервер настроен по инструкции Василия.
Основной смысл моей инструкции именно в том, чтобы не давать сайтам дополнительных прав. Каждый сайт запускается из под своего пользователя и он имеет доступ только к своим файлам.

А теперь представь, что если бы существовал способ делать так, как ты хочешь? Выходит, что любой сайт мог бы выполнять команды от root и в чем тогда защита? Злоумышленник ломает сайт и получает доступ ко всему серверу, ко всем сайтам и файлам.

Так что, ответ прост — это невозможно.

Процесс, который создаёт сайты должен работать не как обычный сайт. Это или отдельный демон на Python, как у нас на хостинге, или отдельный процесс на PHP, но запущенный от root и защищенный по IP от случайного коннекта. Или просто консольные скрипты, как в моей инструкции.

Когда я делал тестовые сайты, там использовались именно эти скрипты, которые запускались по расписанию раз в n минут.
То есть, юзер заходил на обычный сайт, заказывал себе тест, запрос сохранялся в БД, а сервер уже раз в 10 минут от root запускал скрипт, который проверял заказы в БД, создавал тестовые сайты и отправлял на email уведомления об этом.

Ты можешь сделать так же.
Василий Наумкин
04 января 2015, 13:16
4
0
1. Обнови mSearch2 и pdoTools до последних версий
2. Вызови mFilter2
[[!mFilter2?
	&parents=`0`
	&limit=`5`
	&element=`pdoResources`
	&ajaxMode=`button`
	&ajaxElemWrapper=`#mse2_mfilter`
	&ajaxElemRows=`#mse2_results`
	&ajaxElemPagination=`#mse2_pagination`
	&ajaxElemLink=`#mse2_pagination a`
	&ajaxElemMore=`#mse2_mfilter .btn-more`
]]
3. Допиши javascript для скрытия кнопки на странице, когда больше нечего показывать:
$(document).on('mse2_load', function(e, response) {
	var data = response.data;
	var total = data.total;
	var limit = mse2Config['limit'] || mse2Config['start_limit'];
	var page = pdoHash.get()['page'] || 1;
	
	var more = $('#mse2_mfilter .btn-more');
	if (page * limit >= total) {
		more.hide();
	}
	else if (more.is(':hidden')) {
		more.show();
	}
	//setMore();
});
4. По желанию можно добавить функцию setMore, которая будет писать сколько загрузится результатов на кнопке:
function setMore() {
	var btn = $('#mse2_mfilter .btn-more');
	var total = $(mSearch2.options['total']).text();
	var page = pdoHash.get()['page'] || 1;
	var limit = mse2Config['limit'] || mse2Config['start_limit'];
	
	var remains = total - (page * limit);
	if (remains > limit) {
		remains = limit;
	}
	
	var results = 'результатов';
	number = Math.abs(remains);
	number %= 100;
	if (number >= 5 && number <= 20) {
		results = 'результатов';
	}
	number %= 10;
	if (number == 1) {
		results = 'результат';
	}
	if (number >= 2 && number <= 4) {
		results = 'результата';
	}
	
	btn.text('Еще ' + remains + ' ' + results);
}

Вызов этой функции нужно раскомментировать в конце первой функции и добавить при загрузке страницы:
$(document).ready(function() {
	window.setTimeout(function() {
		setMore();
	}, 500);
});

Вот так у меня на тесте всё работает, даже пагинация кнопками. Вот вся тестовая страница.
Василий Наумкин
31 декабря 2014, 08:53
2
+2
Вот так немного симпатичнее:
$q = $modx->newQuery('TicketFile');
$q->limit(1);
$q->select('thumb');
$q->where(array(
	'parent' => $input
));
if ($q->prepare() && $q->stmt->execute()) {
	return $q->stmt->fetch(PDO::FETCH_COLUMN);
}
Константин
30 декабря 2014, 23:19
1
0
На всякий случай выложу пример который я использую, может кто поправит, а если не требуется, то может кому пригодится.

$q = $modx->newQuery( 'TicketFile' );
$q->limit( 1 );
$q->where(array(
   'parent' => $input
));

$q->prepare();
$q->stmt->execute();
$res = $q->stmt->fetchAll( PDO::FETCH_ASSOC );

foreach ( $res as $thumb ) {
    return $thumb['TicketFile_thumb'];
}

Вызывается в чанке таким макаром:

[[+id:getThumb]]
Алексей Ерохин
20 ноября 2014, 20:25
4
+2
Я так делал.
Перед собственно определением плагина в js-файле добавлял это:
var storesex = new Ext.data.ArrayStore({
    id: 'minishop2-product-sex'
    ,fields: [{name: 'name', type: 'string']}
    ,data: [['Мужской'],['Женский'],['Унисекс']]
});
miniShop2.combo.ProductSex = function(config) {
    config = config || {};
    Ext.applyIf(config,{
        store: storesex
        ,emptyText: _('ms2_combo_select')
        ,displayField: 'name'
        ,valueField: 'name'
        ,hiddenName: 'sex'
        ,mode: 'local'
        ,triggerAction: 'all'
        ,editable: false
        ,selectOnFocus: false
        ,preventRender: true
        ,forceSelection: true
        ,enableKeyEvents: true
    });
    miniShop2.combo.ProductSex.superclass.constructor.call(this,config);
};
Ext.extend(miniShop2.combo.ProductSex,MODx.combo.ComboBox);
Ext.reg('minishop2-combo-product-sex',miniShop2.combo.ProductSex);
Теперь в плагине можно использовать xtype только что зарегистрированного комбобокса

sex: {xtype: 'minishop2-combo-product-sex',fieldLabel: 'Пол'}
Наумов Алексей
14 ноября 2014, 17:36
2
+1
<?php
if (!$modx->user->hasSessionContext('web') ) {
  die('Not logged!');
}

$prefix = trim($modx->getOption('placeholderPrefix', $scriptProperties, 'user.'));

$profile = $modx->user->getOne('Profile');

if(isset($_REQUEST['update']) && $_REQUEST['update'] == '1'){
    
    // имя
    $fullname = (isset($_REQUEST['fullname']) && !empty($_REQUEST['fullname'])) ? $_REQUEST['fullname'] : $profile->get('fullname');
    $fullname = trim(strip_tags($fullname));
    if(empty($fullname)){
        $fullname = 'Без имени';
    }
    $profile->set('fullname', $fullname);
    
    //фото
    $clearphoto = (isset($_REQUEST['clearphoto']) && $_REQUEST['clearphoto'] == '1');
    if($clearphoto){
        $profile->set('photo', '');
    }else{
        $photoConfig = array('w' => 48,'h' => 48,'zc' => 1,'bg' => '#fff','f' => 'png');
        // если выбран файл
        if(isset($_FILES['photo']) && isset($_FILES['photo']['tmp_name']) && !empty($_FILES['photo']['tmp_name'])){
            $file_info = getimagesize($_FILES['photo']['tmp_name']);
            if(!empty($file_info)){
                // пути
                $photoFileName = substr(uniqid(), 8).'.png';
                $newPath = 'uploads/users/'.$modx->user->get('id').'/';
                
                // проверяем каталог
                if(!file_exists(MODX_BASE_PATH.$newPath)){
                    if(!mkdir(MODX_BASE_PATH.$newPath, 0777, true)){
                        echo "Can`t create directory ".$newPath;
                    }
                }
                // перемещаем файл
                if(move_uploaded_file($_FILES['photo']['tmp_name'], MODX_BASE_PATH.$newPath.$photoFileName)){
                    // подключаем phpthumb
                    require_once MODX_CORE_PATH.'model/phpthumb/phpthumb.class.php';
                    // инициализируем
                    $phpThumb = new phpThumb();
                    $phpThumb->setSourceFilename(MODX_BASE_PATH.$newPath.$photoFileName);
                    foreach ($photoConfig as $k => $v) {
                        $phpThumb->setParameter($k, $v);
                    }
                    
                    // генерируем файл
                    if ($phpThumb->GenerateThumbnail()) {
                        
                        if ($phpThumb->RenderToFile(MODX_BASE_PATH.$newPath.$photoFileName)) {
                            // устанавливаем права на файл, это опционально, зависит от сервера
                            chmod(MODX_BASE_PATH.$newPath.$photoFileName, 0666);
                            // сохраняем изменения
                            $profile->set('photo', $newPath.$photoFileName);
                        }
                    }
                }
            }
        }
    }
    
    // вызываем событие OnBeforeUserFormSave
    $modx->invokeEvent('OnBeforeUserFormSave',array(
  	'mode' => modSystemEvent::MODE_UPD
		,'id' => $modx->user->get('id')
		,'user' => &$modx->user
		,'profile' => &$profile
	));

    // сохраняем все изменения
    $profile->save();
    $modx->user->save();

    // вызываем событие OnBeforeUserFormSave
    $modx->invokeEvent('OnUserFormSave',array(
		'mode' => modSystemEvent::MODE_UPD
		,'id' => $modx->user->get('id')
		,'user' => &$modx->user
		,'profile' => &$profile
	));
    // устанавливаем плейсхолдер, чтобы показать сообщение
    $modx->setPlaceholder($prefix.'success', '1');
}

// устанавливаем плейсхолдеры для отображения информации
$modx->setPlaceholder($prefix.'fullname', $profile->get('fullname'));
$modx->setPlaceholder($prefix.'photo', $profile->get('photo'));

return '';
Форму надеюсь с полями сами сделаете.

Поле clearphoto — это checkbox, типа «Удалить аватар».