Как я воевал с контекстами

в 0:25, , рубрики: modx, modx revolution, контексты, метки: ,

Как истинный консерватор, я долгое время использовал исключительно MODx Evolution. Меня устраивало прежде всего наличие исчерпывающей документации, кучи статей и предельно понятной архитектуры данной версии MODx CMF. О версии Revolution я периодически читал разные статьи, но мне не хотелось менять привычную для меня систему на что-либо другое. Однако, в один прекрасный момент количество таблиц в базе данных моего мультидоменного «хомячка» на хостинге достигло устрашающих размеров. Встал вопрос о мультидоменном решении. Когда-то я вычитал о возможности создать на MODx Revolution мультидоменный сайт. Я установил движок на тестовый поддомен и начал копать глубже. Как оказалось, в самом фреймворке как такового готового решения насчёт мультидоменности пока не существует. Существует некая система контекстов. Разные контексты можно определить на разные поддомены. Только для этого нужно править файл index.php.

Первое, что приходит в голову — это использовать уже проверенный популярный код:

switch($_SERVER['SERVER_NAME']) {
  case 'sub1.domain.tld': $modx->initialize('sub1'); break;
  case 'sub2.domain.tld': $modx->initialize('sub2'); break;
  case 'sub3.domain.tld': $modx->initialize('sub3'); break;
  default: $modx->initialize('web');
}

Если, скажем, у Вас на системе контекстов зиждется многоязычность, тогда можно сделать даже так:

switch($_SERVER['SERVER_NAME']) {
  case 'domain.ru': case 'www.domain.ru': $modx->initialize('ru'); break; // Переключаем на русский
  case 'domain.fr': case 'www.domain.fr': $modx->initialize('fr'); break; // Переключаем на французский (для примера)
  default: $modx->initialize('web');
}

И для не сильно больших порталов, где число поддоменов контролируется администратором хостинга/домена и редко меняется, такого решения хватит за глаза. Однако, давайте с Вами пофантазируем. Есть у Вас свой сервер. Это может быть Ваш личный сервер или VDS-ка на любимом хостинге. У Вас есть возможность программно создавать поддомены. Предположим, что Вы пишете свой аналог livejournal.com…

Создать контекст при помощи API не сильно сложно. Вдаваться в подробности я не стану, не слишком сильно пока что изучил API MODx Revolution. Тем не менее создать контекст и поддомен — это одно, а вот связать это воедино — другое. Здесь вышеуказанные решения не подойдут, ибо заранее неизвестно сколько будет поддоменов и как будут называться контексты для них. По идее, если алиасы контекстов совпадают с именами поддоменов, тогда вполне подойдёт решение:

define("myRootDomain","domain.tld");
$ctxKey = 'web';

if (preg_match('#(w+).'.myRootDomain.'#si',$_SERVER['SERVER_NAME']) > 0) {
  $ctxKey = preg_replace('#(w+).'.myRootDomain.'#si','1',$_SERVER['SERVER_NAME']);
  if ($ctxKey == 'www') $ctxKey = 'web';
}

Основная информация о контекстах в MODx хранится в БД в таблицах context, context_setting. В первой таблице находятся описания контекстов (ключ, описание, порядок отображения). Во второй — настройки контекста. Помните, в распространённых решениях нам нужно было прописывать страницы ошибок, хост и тому подобное? Вот это-то всё там и хранится. И первое, что приходит в голову — это SQL-запрос к этой таблице:

$SQL = "SELECT * FROM ".$table_prefix." WHERE `key`='http_host' AND `value`='".$_SERVER['SERVER_NAME']."'";

Если бы система контекстов была предусмотрена в старушке Evolution, тогда с алгоритмом всё было бы просто:

$ctxKey = 'web';
if ($result = $modx->db->query($SQL)) if ($row = mysql_fetch_assoc($result)) $ctxKey = $row['context_key'];

Однако, в этом отношении разработчики MODx подложили разработчикам, использующим MODx, небольшую хрюшку, ибо архитектура MODx Revolution зиждется на xPDO. А это уже не привычное нам API, а совсем другой разговор.

Изучив кучу результатов гугло-поиска, в том числе и официальную документацию на MODx Revolution API, я так и не смог понять, как проще сделать запрос к БД в MODx Revolution. Зато, копнув файл core/model/modx/modx.php, я обнаружил нечто следующее:

$pluginEventTbl= $this->getTableName('modPluginEvent');
$eventTbl= $this->getTableName('modEvent');
$pluginTbl= $this->getTableName('modPlugin');
$propsetTbl= $this->getTableName('modPropertySet');
$sql= "
    SELECT
        Event.name AS event,
        PluginEvent.pluginid,
        PropertySet.name AS propertyset
    FROM {$pluginEventTbl} PluginEvent
        INNER JOIN {$pluginTbl} Plugin ON Plugin.id = PluginEvent.pluginid AND Plugin.disabled = 0
        INNER JOIN {$eventTbl} Event ON {$service} Event.name = PluginEvent.event
        LEFT JOIN {$propsetTbl} PropertySet ON PluginEvent.propertyset = PropertySet.id
    ORDER BY Event.name, PluginEvent.priority ASC
";
$stmt= $this->prepare($sql);
if ($stmt && $stmt->execute()) {
    while ($ee = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $eventElementMap[$ee['event']][(string) $ee['pluginid']]= $ee['pluginid'] . (!empty($ee['propertyset']) ? ':' . $ee['propertyset'] : '');
    }
}

Это фрагмент метода getEventMap класса modX. Логично предположить, что вместо длиннющего запроса мы можем вставить свой запрос и он по идее должен отработать, как нужно. В результате рождается решение:

$ctxCur = 'web';
$ctxQur = "SELECT * FROM `".$table_prefix."context_setting` WHERE `key`='http_host' AND `value`='".$_SERVER['SERVER_NAME']."'";
$ctxSQL = $modx->prepare($ctxQur);
if ($ctxSQL && $ctxSQL->execute()) if ($ctxRes = $ctxSQL->fetch(PDO::FETCH_ASSOC)) $ctxCur = $ctxRes['context_key'];

$modx->initialize($ctxCur);

При использовании данного решения нам нужно заботиться лишь о правильном указании поля http_host в админке. И имя контекста в этом случае не обязательно должно совпадать с поддоменом. На сим всё. Спасибо за внимание к моему очередному велосипеду!

Автор: XanderBass

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js