Выводим в Top блогов количество постов вместо рейтинга

Для начала находим Экшен блока. Структура LS подробно описана, поэтому легко находим нужный блок: /classes/blocks/BlockBlogs.class.php В блоке вызывается единственная функция: Blog_GetBlogsRating, с ней и будем работать.

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

Шаг 1. Инициализируем модуль
Есть достаточно много статей описывающих как написать простой модуль, поэтому останавливаться на этом вопросе не будем. Создаем папку /plugins/topblogs2 и в ней делаем XML описание плагина, файл plugin.xml:
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
    <name>
        <lang name="default">TopBlogs2</lang>
    </name>
    <author>
        <lang name="default">Begetan</lang>
    </author>
    <homepage></homepage>
    <version>0.1</version>
    <requires>
        <livestreet>0.5.0</livestreet>
        <plugins>
        </plugins>
    </requires>
    <description>
        <lang name="default">Show top rated blogs by count of posts instead of standart LiveStreet blog's rating</lang>
        <lang name="russian">Выводит топ блогов по количеству постов, а не по стандартному рейтингу блогов LiveStreet</lang>
    </description>
</plugin>


Далее создаем класс активации нашего плагина — /plugins/topblogs2/PluginTopblogs2.class.php:
if (!class_exists('Plugin')) die('Are you sure?');

class PluginTopBlogs2 extends Plugin
{
    protected $aInherits=array(
        'module' => array('ModuleBlog' => '_ModuleTop'),
    );

    public function Activate() {
        return true;
    }

    public function Init() {
    }
}

Шаг 2. Пишем код модуля
Согласно тем названиям, которые мы придумали и объявили в конфигурации плагина код модуля должен находится в файле: /plugins/topblogs2/classes/modules/top/Top.class.php:
<?php
class PluginTopBlogs2_ModuleTop extends PluginTopBlogs2_Inherit_ModuleBlog {

        protected $oMapperBlog2;

        public function GetBlogsRating($iCurrPage,$iPerPage) {

                $this->oMapperBlog2 = Engine::GetMapper ( __CLASS__ );

#               $aResult=parent::GetBlogsRating($iCurrPage, $iPerPage);
                $aResult=$this->GetTopBlogsByPosts($iCurrPage, $iPerPage);
                return $aResult;
        }

        public function GetTopBlogsByPosts($iCurrPage,$iPerPage) {
                list ($aTopBlogs, $aRating) = $this->oMapperBlog2->GetTopBlogsByPosts($iCount,$iCurrPage,$iPerPage);
                $data = array('collection'=>$aTopBlogs,'count'=>$iCount);
                $aBlogs = parent::GetBlogsAdditionalData($data['collection'],array('owner'=>array(),'relation_user'));
                # Подменяем рейтинг блога на новый
                foreach ( $aBlogs as $oBlog ) {
                        $oBlog->setRating($aRating[$oBlog->getId()]);
                        $data2[] = $oBlog;
                }
                # Обновляем список блогов
                $data['collection'] = $data2;
                return $data;
        }
}
?>

Сначала объявляем стандартную функцию GetBlogsRating для того, чтобы переопределить ее в нашем файле. Затем из нее мы вызываем уже нашу функцию GetTopBlogsByPosts, которая и будет делать то что нам нужно. При отладке может возникнуть желание вызвать исходную функцию GetBlogsRating, пример такого вызова в строчке с комментарием.

Обратите внимание, что мы инициализировали свой Маппер, поскольку нам потребуется еще работа с базой данных.
Не применяется кеширование, поскольку мы получаем из Маппера два массива. Придумывать корректный алгоритм было лень, тем более что в моих проектах пока проблемы с нагрузкой не стоит.

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

Шаг 3. Пишем код маппера
Маппер у нас окажется тут: /plugins/topblogs2/classes/modules/top/mapper/Top.mapper.class.php.
<?php

class PluginTopBlogs2_ModuleTop_MapperTop  extends Mapper {

        public function GetTopBlogsByPosts (&$iCount,$iCurrPage,$iPerPage) {
                $sql = "SELECT t.blog_id, COUNT(t.blog_id) as rating " .
                        "FROM ".Config::Get('db.table.topic')." as t " .
                        "GROUP BY t.blog_id " .
                        "ORDER BY 2 DESC " .
                        "LIMIT ?d, ?d";

                $aReturn=array();
                $aReturn2=array();
                if ($aRows=$this->oDb->selectPage($iCount,$sql,($iCurrPage-1)*$iPerPage, $iPerPage)) {
                        foreach ($aRows as $aRow) {
                                $aReturn[]=$aRow['blog_id'];
                                $aReturn2[$aRow['blog_id']]=$aRow['rating'];
                        }
                }
                return array ($aReturn, $aReturn2);
        }
}
?>


В отличие от стандартной функции мы возвращаем два массива — массив ID найденных блогов и ассоциативный массив блог->рейтинг. Сделано так потому что в стандартном объекте «блог» нет значения количества постов. Наверно можно было расширить свойства блога, но так было проще. Буду рад услышать предложения и идеи как изменить или улучшить код.

Аналогично нужно изменить функции GetBlogsRatingJoin и GetBlogsRatingSelf чтобы получился законченный модуль.

Удаление UTF-8 BOM из консоли

linux (debian)
find путь/к/лайвстриту/ -type f \( -name '*.tpl' -o -name '*.php' \) -exec sed -i '1 s/^\xef\xbb\xbf//' {} \;

freebsd (7.2)
find ./ -type f \( -name '*.tpl' -o -name '*.php' \) -exec sed -i -e '1 s/^\xef\xbb\xbf//' {} \;

sed почему-то иногда не отрабатывает, поэтому php, find, awk, xargs:
php -r "ob_start();
passthru('find ./ -type f \( -name \'*.tpl\' -o -name \'*.php\' \) -print0 | xargs -0r awk \'/^\xEF\xBB\xBF/ {print FILENAME}{nextfile}\'');
foreach(array_filter(explode(\"\n\",ob_get_clean())) as \$f){
        echo \"remove BOM from \$f...\n\";
        file_put_contents(\$f, substr(file_get_contents(\$f), 3));
};"

Всё что вам нужно знать про память Windows. Читать от начала и до конца. Ну, про 333 можно не читать

Одна простая вещь

Если вы хотите защитить важную директорию от удаления вследствие небрежного обращения с командой rm -rf *, то создайте файл с именем -i, это заставит rm запросить подтверждение:
touch ~/important-directory/-i

Памятка PHP разработчика

Настройки PHP при разработке в качестве рекомендации

1) error_reporting = E_ALL | E_STRICT
2) display_errors = On
3) register_globals,register_long_arrays,magic_quotes off (первые 2 удалены в 5.4)
4) short_open_tag off
5) allow_call_time_pass_reference = off (Удалено в 5.4)
6) safe_mode — все настройки off (Удалено в 5.4)

Типичные уязвимости
Загрузка файлов:
1. Заливка исполняемого файла
2. Исполнение неопределённых Mime Type Apache например file.php.7z
3. Исполнение мета-информации например исполняемый image.gif
4. Уязвимость связки php+nginx (выставить cgi.fix_pathinfo=0)
5. Выставление некорректных прав на загружаемый файл

Код:
1. Ядовитый ноль он же нулевой байт он же символ конца строки %00, позволяет производить php injection
2. Не фильтрованный ввод
3. SQL injection
4. XSS
5. Remote File Inclusion [RFI] (отключаем allow_url_include = Off)
6. Возможность перехвата сессии (привязываем к клиенту)
7. Rainbow Table подбор ( храним пароли в sault md5)
8. Автоматизированное внедрение iframe (иногда помогает вынос точки выхода из приложение в файл отличный от index + exit в конце) (Такая простая мера очень часто меня спасала)
9. CSRF — Cross-Site Request Forgery (имитирование запроса пользователя к стороннему сайту)

Читать дальше →

Highload оптимизация

Зачастую, «бутылочным горлышком» вашего приложения является база данных, таким образом перво-наперво включаем slow query log и смотрим какой запрос у нас самый медленный, и думаем что с ним делать, если не можем вкурить проблему — зовём старших, пусть тоже повтыкают в EXPLAIN (хабр) вашего чудо-запроса.

Но, опять же ссылаясь к моему опыту, большинство проблем с БД решают правильные индексы. Легко запомнить, что индексировать следует внешние ключи, и всё что у вас в WHERE, ORDER BY, GROUP BY (список не полон, для начала – самое оно).

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

Поиск с использованием LIKE это плохо. Полнотекстовый с MyISAM уже лучше. Внешний аля Sphinx — рулит и бибикает для MySQL и PostgreSQL, инфа достоверная 100%.

Но это полбеды, проблем в БД может подкинуть и само приложение — обращение к БД в цикле/рекурсии или еще каким извращенным способом могут привносить удивительные поправки в результаты нагрузочного тестирования. Сделайте простой профайлер ваших запрос и проследите на каких страницах количество запросов начинает зашкаливать (особенно это касается типа-ORM и почти-Active Record, когда один объект = один запрос, или даже не один). Всем кто уповает на магию фреймворков, иль каких-нить gem-ов — не надейтесь, всё о чём я написал в равной степени относится к большинству языков web-программирования, г… код есть везде, он вездесущ.

Ну, а теперь о главном, нет о главной странице в 1,5 метра — дождется ли её загрузки пользователь со скоростью доступа в 256кбит? Клиентская оптимизация должна проводиться в обязательном порядке: YSlow да Page Speed вам в зубы. Да если погуглить, то даже небольшая правка htaccess для apache улучшит ситуацию:
# Enable ETag
FileETag MTime Size

# Enable Deflate
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/x-javascript

<ifModule mod_expires.c>
  ExpiresActive On
  ExpiresDefault "access plus 1 seconds"
  ExpiresByType text/html "access plus 1 seconds"
  ExpiresByType image/x-icon "access plus 2592000 seconds"
  ExpiresByType image/gif "access plus 2592000 seconds"
  ExpiresByType image/jpeg "access plus 2592000 seconds"
  ExpiresByType image/png "access plus 2592000 seconds"
  ExpiresByType text/css "access plus 604800 seconds"
  ExpiresByType text/javascript "access plus 216000 seconds"
  ExpiresByType application/x-javascript "access plus 216000 seconds"
</ifModule>

Пожмите JavaScript и CSS, да переключите jQuery на Google CDN.

Полезные запросы, чтобы очистить БД в wp

Очищает всякие левые символы. Когда кодирует БД туда сюда.
UPDATE wp_posts SET post_content = REPLACE(post_content, '“', '“');
UPDATE wp_posts SET post_content = REPLACE(post_content, '”', '”');
UPDATE wp_posts SET post_content = REPLACE(post_content, '’', '’');
UPDATE wp_posts SET post_content = REPLACE(post_content, '‘', '‘');
UPDATE wp_posts SET post_content = REPLACE(post_content, '—', '–');
UPDATE wp_posts SET post_content = REPLACE(post_content, '–', '—');
UPDATE wp_posts SET post_content = REPLACE(post_content, '•', '-');
UPDATE wp_posts SET post_content = REPLACE(post_content, '…', '…');

UPDATE wp_comments SET comment_content = REPLACE(comment_content, '“', '“');
UPDATE wp_comments SET comment_content = REPLACE(comment_content, '”', '”');
UPDATE wp_comments SET comment_content = REPLACE(comment_content, '’', '’');
UPDATE wp_comments SET comment_content = REPLACE(comment_content, '‘', '‘');
UPDATE wp_comments SET comment_content = REPLACE(comment_content, '—', '–');
UPDATE wp_comments SET comment_content = REPLACE(comment_content, '–', '—');
UPDATE wp_comments SET comment_content = REPLACE(comment_content, '•', '-');
UPDATE wp_comments SET comment_content = REPLACE(comment_content, '…', '…');


Запрос, чтобы закрыть все «обратные ссылки» на свои посты. Ну функция пингбеки знаете такая есть. Не будешь же каждую запись вручную ;)
UPDATE wp_posts SET ping_status = 'closed';


Очищает все неиспользуемые шорткоды.
UPDATE wp_post SET post_content = replace(post_content, '[tweet]', '' ) ;


Удаляет некорректные метки
DELETE FROM wp_postmeta WHERE meta_key = 'YourMetaKey';


Удаляет все неиспользуемые и давно забытые метки, которые не принадлежат постам, но остались в базе.
DELETE FROM wp_terms WHERE term_id IN (SELECT term_id FROM wp_term_taxonomy WHERE count = 0 );
DELETE FROM wp_term_taxonomy WHERE term_id not IN (SELECT term_id FROM wp_terms);
DELETE FROM wp_term_relationships WHERE term_taxonomy_id not IN (SELECT term_taxonomy_id FROM wp_term_taxonomy);


Очистка кеша фидов.
DELETE FROM `wp_options` WHERE `option_name` LIKE ('_transient%_feed_%')


Удаляет все копии сообщений и историю изменений.!!! Вот это очень хорошо очищает. Ибо после каждого редактирования сохраняется копия правок.
DELETE a,b,c FROM wp_posts a WHERE a.post_type = 'revision' LEFT JOIN wp_term_relationships b ON (a.ID = b.object_id) LEFT JOIN wp_postmeta c ON (a.ID = c.post_id);


Пакетное удаление старых сообщений
В данном примере устанавливается для удаления любой пост, который старше 600 дней.
DELETE FROM `wp_posts`
WHERE `post_type` = 'post'
AND DATEDIFF(NOW(), `post_date`) > 600


Изменить автора у всех сообщений сразу.
SELECT ID, display_name FROM wp_users;

UPDATE wp_posts SET post_author=NEW_AUTHOR_ID WHERE post_author=OLD_AUTHOR_ID;

Как убить тяжелый MySQL запрос?

1. Зайти на сервер по SSH.
2. Выполнить
$ mysql -u USER -p
USER — это ваш пользователь MySQL
3. Ввести пароль для USER.
4. Выполнить
show processlist;
(не забудьте про точку с запятой в конце)
5. Узнать идентификатор тяжелого запроса.
6. Выполнить
kill query 12345;
где 12345 заменить на идентификатор запроса.

Как узнать, какие модули Apache загружены в текущий момент?

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

Зайти на сервер по SSH и выполнить
apachectl -t -D DUMP_MODULES