MODX
LiveStreet
17 October 2012

Релиз новой версии модуля modLivestreet 0.3.0-rc

Продолжая нашу тему про модуль связки MODX и Livestreet, представляю новую версию модуля modLivestreet: livestreet-0.3.0-rc.transport.zip

Что нового? Да практически все :-) Модуль переписан с нуля.
1. Серьезно изменена (улучшена) логика обработки запросов на LiveStreet.
2. Добавлена синхронизированная регистрация пользователей в MODX и LiveStreet. Теперь регистрируя пользователя через админку MODX, пользователь автоматически создается в LiveStreet, а обрабатывая запрос на LiveStreet на регистрацию пользователя, регистрация проходит через MODX, что в свою очередь так же обеспечивает синхронную регистрацию пользователя в обоих движках.
Данную функцию можно отключать через настройку.

Под катом схемы работы (упрощенные) MODX в стандартной версии и с модулем modLivestreet и более подробное описание того, как происходит синхронная регистрация пользователей в MODX и Livestreet (Схему, как изменилась регистрация в MODX выложу позже), а так же исходники.

UPD: сборка пакета на github: github.com/Fi1osof/modx-livestreet

Обещанные схемы работы MODX в стандартной сборке и с модулем modLivestreet



Так как же работает этот модуль?

1. Самый главный плагин в данном модуле срабатывает на два события — onHandleRequest и onPageNotFound.
В первом случае происходит проверка на запросы к Livestreet в принципе (и инициализация переменных Livestreet), а так же проверка на обращение к статическим файлам Livestreet и отдельным его модулям (типа Captcha).
Во втором случае выполняется другая задача: если при обработке запроса MODX нашел целевую страницу, то событие onPageNotFound в принципе не сработает, и LiveStreet обрабатываться не будет, если в документе MODX явно не вызывается сниппет livestreet.run, который и выполняет запрос на LiveStreet. Такой подход позволяет нам не завязывать MODX только на LiveStreet. То есть мы можем сделать на MODX полностью независимый сайт, но сделать раздел /forum/ (или отдельный поддомен forum.site.ru), определить там входящую страницу, прописать в нее вызов сниппета [[!livestreet.ru]] (чтобы обеспечить корень сайта), в настройках указать livestreet.url_perfix '/forum/' и все. Все запросы на маску /forum/.* будут выполняться с вызовом LiveStreet и мы получаем свой блог. Здесь же можно дописать проверку прав или вызов только определенной ветки блога и т.д.
В случае же, если документ не найден и вызывается событие onPageNotFound, и УРЛ соответствует разделу Livestreet, то выполняется уже запрос на Livestreet. Тут маленькая оговорка: если это запрос на регистрацию пользователя Livestreet, то происходит вызов сниппета регистрации пользователь snippet.livestreet.ajax-registration.php

Вот листинг плагина
<?php
switch($modx->context->key){
	case 'web':
		break;
	default: return;
}

switch($modx->event->name){
    // Здесь мы будем проверять возможные запросы к статическому контенту LiveStreet
    case 'OnHandleRequest':
        // Чтобы подгрузить файл-конфиг livestreet_path/config/
        define('IN_MODX', true); 
        define('LIVESTREET_WEB', $modx->getOption('site_url'));
        define('LIVESTREET_PATH', $modx->getOption('livestreet.livestreet_path'));
        define('LIVESTREET_INDEX_FILE', $modx->getOption('livestreet.index_file'));
        define('LIVESTREET_URL_PREFIXE', $modx->getOption('livestreet.url_prefix'));
	$request = false;
        //print "<br />REQUEST_URI:" .  $_SERVER['REQUEST_URI'];

        // Определяем есть ли запрос на LiveStreet
        if($_SERVER['REQUEST_URI'] == LIVESTREET_URL_PREFIXE || $_SERVER['REQUEST_URI']."/" == LIVESTREET_URL_PREFIXE){
            $request = '/';
	}
	else{
            // Если это не раздел LiveStreet, пропускаем
            $preg = str_replace('/', '\/', LIVESTREET_URL_PREFIXE);

            if(!preg_match("/^{$preg}/", $_SERVER['REQUEST_URI']."/")){
                    return;
            }
            $request = preg_replace("/^{$preg}/", '', $_SERVER['REQUEST_URI']);

	}
        if( substr( $request, 0, 1) != '/')  $request = '/'. $request;   
        
        // Фиксируем запрос на LS
        define('LIVESTREET_REQUEST_URI', $request); 

        // Проверяем статус сайта, чтобы не отдавать контент, если сайт закрыт
        if(!$modx->checkSiteStatus()){
                return;
        }

        $file = LIVESTREET_INDEX_FILE;
	
        /*$t = $modx->invokeEvent('onLivestreetUserSave',  array('sdfsdf'));
	print_r($this->processEventResponse($t));
	exit;*/


        // Проверяем на обращение к директории LiveStreet   

        $preg = str_replace('/', '\/', "(/templates/|/uploads/|/engine/lib/external/jquery)");
        if(preg_match("/^{$preg}/", LIVESTREET_REQUEST_URI)){

            $file = LIVESTREET_REQUEST_URI; 
            $file = preg_replace('/\?+.*/', '', $file);

            $fullpath = str_replace('//','/', LIVESTREET_PATH.$file);
            if(!file_exists($fullpath)){
                    die('File Not Found');
            }

            $fsize = filesize($fullpath); 
            $pi = pathinfo( $file);
            $ext = $pi['extension'];
            switch ($ext) {
                case "css": $ctype="text/css; charset=utf-8"; break;
                case "js": $ctype="application/x-javascript; charset=utf-8"; break;
                case "pdf": $ctype="application/pdf"; break;
                case "exe": $ctype="application/octet-stream"; break;
                case "zip": $ctype="application/zip"; break;
                case "doc": $ctype="application/msword"; break;
                case "xls": $ctype="application/vnd.ms-excel"; break;
                case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
                case "gif": $ctype="image/gif"; break;
                case "png": $ctype="image/png"; break;
                case "jpeg":
                case "jpg": $ctype="image/jpg"; break;
                default: $ctype="application/force-download";
            } 

            header("Content-type: {$ctype}", true);  
            header("Content-Length: ".$fsize);
            header("last-modified: ". gmdate("d, d m y h:i:s", filemtime($fullpath) )." GMT"); 
            header("Pragma: public");  
            header("Expires: 0");  
            readfile($fullpath); 
            exit;
        }
        
        /* 
        * Определяем надо ли какой-нибудь модуль запускать
        */
        // Каптча
        $preg = str_replace('/', '\/', "/engine/lib/external/kcaptcha/");
        if(preg_match("/^{$preg}/", LIVESTREET_REQUEST_URI)){
            $file = 'engine/lib/external/kcaptcha/index.php'; 
            require_once LIVESTREET_PATH.$file;
            exit;
        }

        break;

    case 'OnPageNotFound':
        // if not LiveStreet request, stop
        if(!defined('LIVESTREET_REQUEST_URI')){
            return;
        }
        $_SERVER['REQUEST_URI'] = LIVESTREET_REQUEST_URI;

		// Registration
		if(LIVESTREET_REQUEST_URI == $modx->getOption('livestreet.registration_url', null, '/registration/ajax-registration/')){ 

			// if not in sync mode
			if($modx->getOption('livestreet.sync_users') == true){
				print $modx->runSnippet('livestreet.ajax-registration');
				exit;
			}
		}

        print $modx->runSnippet('livestreet.run');
        exit;

        break;
    default:
	$modx->event->output( 'true');
    ;
}

В принципе, как вы видите, плагин совсем не большой для такого функционала. Так что любой разберется, если захочет доработать его под свои нужды.

2. На выполнение запросов на LiveStreet используется сниппет snippet.livestreet.run

Листинг
<?php
if($request_uri){
	$_SERVER['REQUEST_URI'] = $request_uri;
}
ob_start();
    @include $modx->getOption('livestreet.livestreet_path').$modx->getOption('livestreet.index_file'); 
    $output = ob_get_contents();
ob_end_clean();

return $output;

Он совсем маленький. Тупо вызываем LiveStreet, буферизируем и возвращаем результат. Таким образом можно и контент обработать перед выводом, и результаты Ajax-запросов обработать для проверки выполнения (особенно это важно при выполнении регистрации пользователей, так как в случае синхронной регистрации пользователей в MODX и LiveSteet необходимо прервать регистрацию в случае проверки регистрации пользователя как в MODX, так и в Livestreet).

Тут опять оговорка. В Livestreet не совсем грамотно класс вывода контента работает. В режиме вывода Ajax-результатов LS делает тупо exit();, что обламывает работу всего модуля. Потому в LS необходимо сделать маленькую манипуляцию:
В файле components/livestreet/docs/forLiveStreet/source/engine/modules/viewer/Viewer.class.php
в методе DisplayAjax($sType='json') заменить exit(); на return;
а в методе Display($sTemplate) заменить
if ($this->sResponseAjax) {
	$this->DisplayAjax($this->sResponseAjax);
}
на
if ($this->sResponseAjax) {
	$this->DisplayAjax($this->sResponseAjax);
        return;
}

Принципиально это не изменит самостоятельной работы Livesreet, но даст нам возможность обрабатывать результаты Ajax-запросов и продолжать свою логику.

3. Сниппет livestreet.ajax-registration.
Этот сниппет обрабатывает запрос на регистрацию пользователя Livestreet.

Листинг
<?php
// if not in sync mode
if($modx->getOption('livestreet.sync_users') != true){
	return;
}

$path = $modx->getObject('modNamespace', array(
	'name' =>  'livestreet'
)) -> getCorePath()."processors/";
// print $path;

// exit;
$response = $modx->runProcessor('security/user/ajaxCreate', array(
	'username' => $_POST['login'],
	'email'	=> $_POST['mail'],
	'passwordnotifymethod' => 'false',
	'passwordgenmethod' => 'false',
	'specifiedpassword'	=> $_POST['password'],
	'confirmpassword'	=> $_POST['password_confirm'],
), array(
	'processors_path' => $path
));



$bStateError = false;
$sMsgTitle = null;
$sMsg = null;
$errors = array();

if($response->isError()){
	// print '<pre>';
	//print_r($modx->error->errors);




	$errorsArray = (array)$modx->error->errors;
	// processEventResponse
	// $error = $response->getMessage();
	if($errMessageArr = (array)explode("\n", $response->getMessage())){
		foreach($errMessageArr as $message){
			if(!$message = trim($message))continue;
			if(!$errArr = explode('::', $message) OR  count($errArr) != 2){
				$sMsg = $message;
			}
			else{
				$errorsArray[] = array(
					'id' => $errArr[0],
					'msg'	=> $errArr[1],
				); 
			}
		}
	}  

	foreach($errorsArray as $err){
		// LiveStreet Errors
		if($name = $err['id']){
		  switch($name){
		  	case 'username':
				$name =  'login';
				break;
			  case 'specifiedpassword':
				  $name = 'password';
				  break;
			case 'confirmpassword':
				$name = 'password_confirm';
				break;
			  default: continue;
		  }
		  $errors[$name][0] = $err['msg'];
		}
		// MODX errors
		else{

			$sMsg = current($err);
		}
	}

	if(!$errors && !$sMsg){
		$sMsg = 'Ошибка выполнения запроса';
	}
	if($sMsg){
		$sMsgTitle = 'Error';
		$bStateError = true;
	}
	$response = array(
	  'sMsgTitle' => $sMsgTitle,
	  'sMsg'	=> $sMsg,
	  'bStateError'	=> $bStateError,
	  'aErrors'	=> $errors
	);
}
else{
	// Success


	$response = array(
	  'sMsgTitle' => null,
	  'sMsg'	=> 'Поздравляем! Регистрация прошла успешно!',
	  'bStateError'	=> false,
	  'sUrlRedirect' => $_POST['return-path'],
	);

}

return json_encode($response);

Если в настройках отключена синхронная регистрация, то сниппет не выполняется, и если это прямой запрос на регистрацию пользователя в Livestreet, то запрос отправляется на Livestreet и все.
Если синхронная регистрация включена, то запрос отправляется не на Livestreet, а на регистрацию пользвателя в MODX (вызывается процессор core/components/livestreet/processors/security/user/ajaxCreate.class.php, где вызывается расширенный класс modUserCreateProcessor). Суть вызова этого процессора простая — вызвать базовый процессор на создание пользователя MODX через системный процессор modx/processors/security/user/create.class.php
Причина такого вызова в том, что мы не можем напрямую вызывать этот процессор, так как в нем выполняется проверка на право создания новых пользователей, а этого права нет у неавторизованного пользователя. Потому в этом классе мы просто затираем проверку этого права (За подсказку по этому приему отдельное спасибо bezumkin).

Листинг
<?php

$file = MODX_PROCESSORS_PATH.'security/user/create.class.php';  
if(!file_exists($file)){
    class modLivestreetUserCreateErrorProcessor extends modProcessor {
        public function process() {
            $err = 'processor security/user/create not found';
            $this->modx->log(modX::LOG_LEVEL_ERROR, $err);
            return $this->failure($err);
        }
    }
    return 'modLivestreetUserCreateErrorProcessor';
}

require_once $file;

class modLivestreetUserCreateProcessor extends modUserCreateProcessor {
    public $permission = '';
}
return 'modLivestreetUserCreateProcessor';


Ну а когда происходит регистрация пользователя в MODX, мы вешаем наш плагин livestreet_users на событие OnBeforeUserFormSave.

Листинг
<?php
/*
 * Synchronized registration MODX and Livestreet users
 */

switch($modx->event->name){
    case 'OnBeforeUserFormSave':
        switch($scriptProperties['mode']){
            // Register new user
            case 'new':
				// if not in sync mode
                if($modx->getOption('livestreet.sync_users') != true){
					return;
				}
                // Using for LiveStreet ModuleSecurity::GenerateSessionKey



                // check password method
                if($scriptProperties['data']['passwordgenmethod'] == 'g'){
                    $len = $modx->getOption('password_generated_length',null,8);

                    $password = $password_confirm = modUserValidation::generatePassword($len);
                    
                    /*
                     * note then newPassword in createProcessor will not be overwrited !!!
                     * in backend will see wron new passrowd
                     */
                    $scriptProperties['user']->set('password', $password); 
                }
                else{
                    $password = $scriptProperties['data']['specifiedpassword'];
                    $password_confirm = $scriptProperties['data']['confirmpassword'];
                }

                $_REQUEST['password'] = $password;
                $_REQUEST['password_confirm'] = $password_confirm;

                $_REQUEST['login'] = $scriptProperties['data']['username'];
                $_REQUEST['mail'] = $scriptProperties['data']['email'];

                $_REQUEST['login'] = $scriptProperties['data']['username']; 

				if($modx->context->key == 'mgr'){
					$captcha = time();
					$_SESSION['captcha_keystring'] = $captcha;
					$_REQUEST['captcha'] = $captcha;

					$_SESSION['user_id'] = '';

					$_REQUEST['security_ls_key'] =  md5( session_id(). $modx->getOption('livestreet.module.security.hash', null, 'livestreet_security_key'));
				}


                $response = $modx->runSnippet('livestreet.run', array(
                        'request_uri' => $modx->getOption('livestreet.registration_url')
                ));

                $response =  json_decode($response);
                
                if(!is_object($response)){
                        $modx->event->output('Ошибка выполнения запроса');
                }
                elseif($response->aErrors){ 
                    $errors = '';
					$errors__ = array();
                    foreach((array)$response->aErrors as $f => $val){
                        $errors .= "$f:: ". $val[0]."\n";
                    }

                    $modx->event->_output = $errors; 
                }
                return;
                break;
            default:;
        }
        break;
    default:$modx->log(modX::LOG_LEVEL_ERROR, "Wrong Event: ". $modx->event->name);
}

В момент регистрации пользователя MODX, после всех проверок (если все ОК), выполняется плагин и происходит попытка регистрации пользователя в LiveStreet. Если попытка обламывается, возвращаем ошибки LiveStreet и прерываем регистрацию пользователя MODX. Если все ок, то окончательно регистрируем пользователя в MODX.
Таким образом мы добиваемся того, что пользователей можно регить и через админку MODX, и через FrontEnd, и и там, и там мы увидим все возможные ошибки, и получим регистрацию пользователя и в MODX, и в LiveStreet. А в дальнейшем этих пользователей при надобности можем увязать по емейлу (так как в LS емейл — уникальный ключ).

Вот и все :-)
Вот за это я и люблю MODX:-) Мы серьезно изменили работу движка, не затронув и байта кода самого движка.
Но под MODX нет модуля социалки.
А вот Livestreet — отличная социалка, но она не дает такого API, как MODX. У них даже админки как таковой нет.
Но вместе эти движки могут дать отличный продукт! Буду и дальше развивать этот модуль.

UPD 2:Вышел новый релиз: modlivestreet-0.4.1-rc.transport.zip
Новое:
1. Все элементы и настройки теперь имеют префикс modLivestreet, потому к сожалению новый модуль не имеет обратной совместимости с прошлой версией. Но наверняка это ни для кого не будет проблемой. Достаточно удалить старый модуль и поставить новый и в главной странице изменить на
2. Добавлена проверка на расширение скачиваемых файлов из директорий /templates/, /uploads/ и т.п., чтобы не качали .php и т.п.
Конечно подобных файлов там и не должно быть, но как минимум в /skin/config/ такой файл есть.

UPD 3: Заметка по безопасности: modxlivestreet.ru/blog/modLivestreet_security/7.html

+6
4k 18
Comments 40