Explay CMS - бесплатная система управления соц. сетями

Lauri

Новичок
Explay CMS - бесплатная система управления соц. сетями

Доброе время суток! Позвольте я расскажу о своей бесплатной системе управления соц. сетями и блого-социальными сообществами - Explay CMS. CMS распространяется под лицензией GNU GPL 3 версии.

Немного характеристик:
- коллективные блоги
- фотоальбомы
- внутренняя почта
- система комментариев
- система рейтингов
- возможность заводить друзей
- лента друзей (как В Контакте)
- панель администратора

Т.к. это форум прогаммистов, то немного о технических характеристиках.
Пару месяцев назад выпустил 3 версию, написанную с нуля, с использованием всех прелестей PHP 5 и не только (UTF-8, MySQL InnoDB, MVC, ORM, XML, AJAX, XSLT, правильное кеширование). Отличительная особенность моей CMS - это дружелюбность к разработчикам. Благодаря ORM (http://ru.wikipedia.org/wiki/ORM) (при учете, что архитектура CMS построена по принципу MVC) разработка нового функционала (модулей) максимально проста. Для решения 90% задач используется API ядра системы, поэтому разработчику даже не нужно знать SQL. XSLT-шаблоны дают верстальщику настоящую независимость от PHP-разработчика, поскольку результат работы модулей - это только данные в виде XML.

Так же хочу отметить, что CMS имеет систему обновлений (обновляется только программный код, дизайн/шаблоны не затрагиваются). Обновления выпускаются примерно два-три раза в месяц. Чтобы обновиться, администратору достаточно скачать архив с обновлением и загрузить его в панели администратора.

Сейчас я пытаюсь сформировать сообщество разработчиков и пользователей Explay CMS (http://explay.su). Существует документация и FAQ, а все интересующие Вас вопросы Вы можете задать в блогах на сайте или лично мне.

Все сборки выкладываются на Гугл Коде:
http://code.google.com/p/explay-cms/downloads/list
Там же находится и SVN проекта (только он не обновлялся где-то месяц, все руки никак не доходят).

Спасибо за внимание :)
 

Lauri

Новичок
whirlwind, я немного осведомлен о движке livestreet, но как мне кажется Explay и livestreet абсолютно разные, как по архитектуре, так и по стилю кодирования.
 

Lightning

Трудоголик
От XSLT верстальщики вешаются :)
А что подразумевается под "правильным кешированием"?
 

vovanium

Новичок
Хм... Что-то с учетом всех прелестей, главная страничка для гостя генерится за 0,3-0,4 сек, при том что в базе всего 2 поста... Как-то на локалке с четырехядерным процем и 8 гигами памяти, привык к цифрам на порядок-два быстрее.

И еще я конечно все понимаю про удобства программеров и т.п. Но когда для главной страницы, даже без авторизации (т.е. без вытаскивания каких-либо личных данных юзера) уходит 58 SQL запросов, причем половина запросов типа
Код:
SELECT SQL_CACHE * FROM `explay_types` WHERE `id` = 10
SELECT SQL_CACHE * FROM `explay_types` WHERE `id` = 27
и рассуждать при этом о "правильном" кэшировании - мягко говоря глупо... Это примерно то же что на грузовике возить по бутылке пиве, и при этом гордиться что благодаря новому движку экономится целых 5% топлива.
 

DiMA

php.spb.ru
Команда форума
Почитал блог - сплошные хвалебные отзывы =)

Ну, ладно, читаю выборочно доку:

-------------
PHP:
CacheController

$cacheController = CacheController::getInstance (); 

$cacheParams = array ( 
   'key' => 'my_cahce_123', // обязательный ключ 
   'dir' => 'somedir', // каталог для смысловой нагрузки и избежания случайных пересечений ключей (обязательно) 
   'lifetime' => 60*60*5 // время жизни кеша в секундах. По-умолчанию это значение берется из реестра "//settings/cache/default_lifetime" 
); 

if ($object = $cacheController->getObject ($pacheParams)) { 
   if ($object instanceof Object) { 
      exit ("OK"); 
   } else { 
      exit ("bad cahce"); 
   } 
} 

$object = new Object ($oType); 

$acheParams['value'] = $object; // значение, которое может быть записано в кеш 

$cacheController->writeObject ($acheParams);
--------------

Нда, какой запущенный детский сад. Вот, как надо писать кеш.

Итак, у нас есть некий абстрактный метод, который что-то создает. Например, исполняет SQL запрос, генерит шаблон и т.д. Метод принимает ряд аргументов и возвращает все по ретурну. Итого ИЗНАЧАЛЬНО:

PHP:
function selectSome(string $arg1, int $arg2, array $moreParam) {

// вычисления 
return $result;

}
Мой кеш требует АБСТРАКТНО вставить 2 строки в начало и конец этого метода, не вдаваясь в суть. И будет ТАК:

PHP:
function selectSome(string $arg1, int $arg2, array $moreParam) {

if (Cache::Load($cacheName=array("selectSome"), $cacheArgs=array($arg1, $arg2, $moreParam), &$result, self::Cache)) return $result;

// вычисление данных

Cache::Save($cacheName, $cacheArgs, $result, self::Cache);

return $result;

}
Но не забываем про все места, где данные обновляются. Это вообще самое главное в кешах - вовремя их похерить.

PHP:
function updateSome($arg1, $arg2) {

Cache::Delete(array("selectSome"));

// обновление данных

}

Итак.

Первый вызов описывает: общий мульти тег, параметры конкретного объекта, куда вернуть результат (если он уже был кеширован), спец константа для быстрого отключения кешей (для разработки).

Второй вызов. Сохраняет результат под тем же именам.

Третий вызов. В одну команду уничтожает ВСЕ кешированные объекты с общим мультитегом (или можно удалить только один объект, а не все по мультитегу, но как правило нужно удалять разом ВСЕ). Необходимо вызвать, если идет обновление данных, чтобы будущие вычисления не использовали кеш.

Без тщательного продумывания удаления кешированных объектов - проект покрывается многими говнобагами. В моем методе задумываться ни о чем не нужно, чисто копипаст по правилам. Кто знает более красивые и абстрактные методы кеширования - в студию :)

Естественно, такие мелочи, где хранить кеш и сколько описывается в отдельном конфиге (разные настройки по разным мультитегам).
 

DiMA

php.spb.ru
Команда форума
SELECT SQL_CACHE *

Присоединяюсь, это фигня, а не кеш... Работать не будет.


Методы
fetch_array
fetch_row
getCountQueries
getInstance
loadEngine
num_rows
query
reset_pointer
startTransaction
stopTransaction

Вы уж определитесь - либо отстойные подчеркивания, либо нормальный CamelCase

******************************************************
---------------
Защита GET-запросов

Если пользователю доступно выполнение некоторого действия, например, голосование или удаление статьи, где параметры (id и т.д.) передаются через GET, то для предотвращения CSRF-атак рекомендуется пользоваться следующим приемом. К запросу добавлять GET-параметр с названием scode, значение которого - это сессионный защитный ключ пользователя (он передается в XML-таблице ответа модуля в значении атрибута scode тега data, см. XML-таблицы ответов модулей). Затем в вызываемом методе модуля добавить строчку $this->expectSecureGET(); (желательно в начале метода). Теперь метод модуля стработает только при правильном значении GET-параметра scode.

Обратите внимание, что таким образом следует защищать только GET-запросы, в результате которых выполняется удаление, добавление или изменение объектов (особенно это критично дейсвий в панели администратора).

----------

Еще один детский сад. Эта проблема решается так:

1. Гетом только нормальные запросы, которые ничего не делают. Проверять их не нужно.

2. Все пост запросы САМИ вставляют че надо в форму и потом это проверяют. Без участия программера. Заодно и дубли постов не проходят. (Отключается - по запросу, а по-умолчанию - всключено во всех формах)

-~{}~ 28.08.09 23:06:
******************************************************

Берем первый попавшийся странный код, смахивающий на ...

PHP:
function is_true_float($val){
    if(is_float($val) || ((float) $val > (int) $val || strlen($val) != strlen((int) $val)) && (int) $val != 0) {
        return true;
    }
    else {
        return false;
    }
}
Т.к. понять, че тут делается нельзя, ищем вызов функции по всему коду. Опа, а нигде и не используется. Тока в закомментированном блоке:

PHP:
                /*case 'float' : {
                    if (!is_true_float ($fieldValue)) {
                        $aErrors[$fieldName] = lang ('validation_error_value_float',__CLASS__);
                    }
                    break;
                }*/
Блок, похоже, проверяет валидность полей из веб-формы. Афтар... Скока можно рассказывать - из $_REQUEST не может придти ни INT, ни DOUBLE ни вообще нихрена, кроме STRING (или массива строк).

-~{}~ 28.08.09 23:15:

******************************************************

Хахаха, вот это жесть:

PHP:
/**
 * Удаляет из строки все теги кроме разрешенных и потенциально-опасные параметры тегов типа onclick, onload и т.п.
 * @param string $string
 * @return string
 */
function cmsStripTags ($string) {
    $allowedTags = array ('a','img','p','br','ul','ol','li','strong','b',
                          'em','i','strike','s','del','table','tr','td',
                          'abbr','blockquote','quote','pre','code','sub',
                          'sup','acronym','u','cut','video','user','font');
    
    $sTags = '';
    foreach ($allowedTags as $tag) {
        $sTags .= '<' . $tag . '>';
    }
    
    $string = strip_tags ($string, $sTags);
    
    $string = preg_replace ("#onclick=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onmouseover=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onmouseout=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onmousedown=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onmouseup=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onselect=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onfocus=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onblur=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onload=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onkeydown=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onkeyup=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#ondblclick=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onunload=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onmouseup=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onsubmit=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#onerror=[\"|\'](.+?)[\"|\']#iu", '', $string);
    $string = preg_replace ("#style=[\"|\'](.+?)[\"|\']#iu", '', $string);
    
    return ($string);
}
Естественно, эта детская поделка ничего не удалит. Даже школьник допрет, куда вставить явакод помимо вырезанных тегов (кроме этих событий, есть еще много других мест).

Полнейшая бредятина с регами. Ну, понятно, их нужно объединить до одного, чтобы зря процессор не гоянть:

PHP:
    $string = preg_replace ("#(onclick|onmouseover| ... )=[\"|\'](.+?)[\"|\']#iu", '', $string);
Но этот рег не найдет даже такой примитив в ONCLICK, который проверяется :)

Код:
<a onclick = alert(& quot;Hello & quot;)> test </a> (между & и q удалить пробел - особенности форума)
Короче, защита крутая, от таких же писателей говноцмс :)

-~{}~ 28.08.09 23:17:

******************************************************

PHP:
/**
 * Преобразует символы строки в нижний регистр, заменяет русские буквы на созвучные латинские и экранирует/заменяет некоторые знаки препинания
 * @param string $string
 * @return string
 */
function translit ($string) {
    $aFrom = array ('й','ц','у','к','е','н','г','ш', 'щ', 'з','х','ъ','ф','ы','в','а','п','р','о','л','д','ж','э','я', 'ч', 'с','м','и','т','ь','б','ю', 'ё');
    $aTo   = array ('y','c','u','k','e','n','g','sh','sh','z','h','', 'f','i','v','a','p','r','o','l','d','j','e','ya','ch','s','m','i','t','', 'b','yu','e');

    $string = preg_replace ($aFrom, $aTo, $string);
    
    return $string;
}
Про функцию strtr() афтар конечно не вкурсах.

-~{}~ 28.08.09 23:25:

******************************************************


Встречается 3 забавных места

PHP:
1.        eval ('$oCron->' . $callMethod . ' ();');


2.            /*if ($realModuleName == $moduleName) {
                eval ('$oResponse = $this->modules[\'' . $moduleName . '\'][\'object\']->' . $method . ' (' . $sParams . ');');
            } else {
                $oResponse = $this->modules[$moduleName]['object']->__call ($method, $aParams);
            }*/
            
            //$oResponse = $this->modules[$moduleName]['object']->execute ($method, $sParams);
            eval ('$oResponse = $this->modules[\'' . $moduleName . '\'][\'object\']->' . $method . ' (' . $sParams . ');');


3.        eval ('$response = self::$subObjects[$this->moduleName][\'' . $className . '\']->' . $method . ' (' . $sParams . ');');
Во 2 и 3 месте параметры $sParams никак не проверяются и берутся, скорее всего, из УРЛ.
Т.е. запостить туда прямиком свой код не составляется труда:

eval('$this->modules(' ..... " ); $HACK; die( " .... ');');

Воспроизвести дыру не смог из-за банальной до ужаса причины - явно дырявые блоки нигде не вызываются :)
Ну, может плохо искал, но stream_open() не вызывается нигде, хотя и описана.

А глядя на потуги афтара с закомментированным eval, хочется порекомендовать ман на функцию call_user_func_array().

-~{}~ 28.08.09 23:43:

******************************************************


Более тонкий пример ламерства и не понимания работа файловых локов. 100% будут потери данных при нагрузке и параллельных исполнениях этого кода.

PHP:
            $f = fopen ($file, 'w+');
            flock ($f, LOCK_EX);
            fwrite ($f, serialize ($this->aHelpers));
            flock ($f, LOCK_UN);
            fclose ($f);
Между 1 и 2й строкой вклинятся сотни других потоков, которые допишут иные куски данных (в файле будет невалидный мусор).

Часто, такие ошибки делают и вполне профессиональные программисты :)

Короче, FLOCK и "w+" - это заведомый бред. Жалко, что в доке это не написано большими буквами.

Снимать блокировку перед закрытием файла не нужно. Сам снимится на закрытии.

-~{}~ 28.08.09 23:47:

******************************************************

Думаю, КАЖДЫЙ начинающий программер хранит у себя в мега цмс подобную говнофункцию:

PHP:
    private function array2PHPString (array $arr, $arrName) {
        $s = <<<TEXT
<?php
\${$arrName} = array (

TEXT;

        $cntElements = count ($arr);
        $i = 1;
        
        foreach ($arr as $key => $value) {
            $s .= "'" . $key . "' => '" . addslashes($value) . "'";
            
            if ($i != $cntElements) {
                $s .= ",\r\n";
            }
            
            ++$i;
        }
        
        $s .= ");\r\n";
        
        return $s;
    }
На самом деле, пишется это В ОДНУ СТРОКУ: file_put_contents + var_export.

Нахрена использовать бредовую инициализацию $s, вместо простого $s=" .... " - не понятно.

******************************************************

PHP:
function safeSql ($s) {
    if (!get_magic_quotes_gpc()) {
        if (is_string ($s)) {
            $s = mysql_real_escape_string ((string) $s);
            return $s;
        }
        elseif (is_array ($s)) {
            foreach ($s as $key => $v) {
                $s[$key] = mysql_real_escape_string ((string) $v);
            }
            return $s;
        } else {
            return $s; 
        }
    }
    
    return $s;
}
Сначала проверили, а не строка ли это? А потом еще разок, для надежности, и тип привели.

И между делом все ретурны можно выкинуть, логика не изменится.

А т.к. проект работает на UTF-8, то использовать mysql_real_escape_string не обязательно. Достаточно просто addslashes.

******************************************************


PHP:
        if ($this->orderRand) {
            $sSql = $sUnion . "\r\nORDER BY RAND()";
        } else {
Вот так и представляю, как мега социальная сетка будет эту команду исполнять .-)


******************************************************


По всему коду маячит антипаттерн "магические числа".


******************************************************

PHP:
            $size = $size > 10 ? $size : 10;
            $size = $size < 40 ? $size : 40;
Этот кусок пишется так: $size=min( max(10, $size), 40);

По умному - в functions.php пишем between( $x, $min, $max).


******************************************************

Функции ajax_vote_blog и ajax_vote_post содержат ОДИНАКОВЫЙ сплошной блок кода из 27 строк.


******************************************************

В функции getTotalCount афтар демонстрирует полное незнание SQL. Функция строит некий абстрактный селект к базе, используя LIMIT и постраничную навигацию. Разумеется, хочется кроме навигации и знать ОБЩЕЕ ЧИСЛО всех полей, т.е. исполнить тут же запрос без этого LIMIT.

Че делает юзверь? Правильно: 2 запроса. Афтар поступил мудрее: второй запрос оформил через юнион

SELECT .... WHERE ... LIMIT UNION (SELECT COUNT(*), " . (++$n) . " AS '__fuckin_union__' FROM ...

Если почитать доку, можно узнать, что не нужно делать второй запрос. Можно достать общее число строк вместе с LIMIT-запросом.
 

DiMA

php.spb.ru
Команда форума
И с чего бы ей не работать? Русская буква разлогается на 2 байта, которые в UTF-8 строке не могут наложиться вкривь на другие символы (попасть на последний байт одной буквы и первый байт другой).

Код:
Unicode UTF-8:
0x00000000 — 0x0000007F: 0xxxxxxx
0x00000080 — 0x000007FF: 110xxxxx 10xxxxxx
0x00000800 — 0x0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
0x00010000 — 0x001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
 

zerkms

TDD infected
Команда форума
DiMA
напиши функцию, которая с помощью strtr транслитерирует строку в utf8.
 

pilot911

Новичок
DiMA

А т.к. проект работает на UTF-8, то использовать mysql_real_escape_string не обязательно.


почему ?
 

zerkms

TDD infected
Команда форума
pilot911
потому что на главной в кошерном utf8 написана просьба "пожалуйста, не отправляйте кавычек и прочих sql-инъекций". работает 100%. :)
 

Smelo

Новичок
pilot911
потому что addslashes корректно отрабатывает при утф
 

zerkms

TDD infected
Команда форума
Smelo
а mysql_real_escape_string, стало быть, нет? ты представляешь разницу, между этими функциями? зачем и когда должна быть использована каждая из них?
 

Smelo

Новичок
mysql_real_escape_string
требует соединения с БД, мб ещё работает дольше, потому как кодировку определяет

вообщем я сам её пользую, но вы на чела наехали, пришлось вмешаться.
 

Smelo

Новичок
zerkms
скорее в меру добрый дядя =)
настолько добрый, что даже спец. ссылку нашёл
http://raz0r.name/vulnerabilities/sql-inekcii-svyazannye-s-multibajtovymi-kodirovkami-i-addslashes/
 

zerkms

TDD infected
Команда форума
Smelo
угу, и ты скорее слова Димы опротиворечил, чем защитил. ололо? :)
 
Сверху