Наверх

Плагин для транслитерации русских имен файлов загружаемых в ModX Revolution

В разделе «Репосты» расположены чужие статьи, которые мне понравились или показались полезными.

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

Как использовать:
Создаем новый плагин с именем TransliterateFileNames
копируем в плагин код с Github
назначаем на событие OnFileManagerUpload.

Источник: community.modx-cms.ru/blog/2478.html

Здесь добавлю уже от себя. Лично я пользуюсь этой версией плагина:

<?php
switch ($modx->event->name) {
    case 'OnFileManagerUpload':
        $generator = $modx->newObject('modResource');
        $bases = $source->getBases($directory);
        $fullPath = $bases['pathAbsolute'].ltrim($directory,'/');
        $directory = $source->fileHandler->make($fullPath);
        foreach ($files as $file) {
            $ext = @pathinfo($file['name'],PATHINFO_EXTENSION);
            rename($directory->getPath().$file['name'], $directory->getPath() .
            str_replace($ext, '.'.$ext, $generator->cleanAlias($file['name'])));
        }
        break;
    default: break;
}
return true;

Дело в том, что в MODX уже заложены основы для реализации автоматических транслитераций или переводов для псевдонимов страниц. Например, можно установить плагин Translit, который будет автоматически транслитерировать адреса страниц. Или плагин yTranslit, который будет переводить адреса на английский с помощью сервиса Яндекс-переводов.

Так вот эта версия плагина позволяет использовать принятую на сайте систему транслитерации/перевода и загружаемые файлы будут легко вписываться в общую картину сайта.


34 комментария

  1. Саша Друмс 02 июля 2014, 18:45(Комментарий был изменён) # 0
    В твоей версии при транслитерации в имени ставится лишняя точка перед расширением файла.
    Если убрать эту точку из str_replace($ext, '.'.$ext ничего страшного не произойдет? Не силен в php, к сожалению.
    1. Илья Уткин 03 июля 2014, 07:51 # +1
      Да. Дело в том, что я привык настраивать правила для псевдонимов так, чтобы в них точки, запятые и прочие знаки препинания вырезались, поэтому добавил эту строчку. Код без нее будет выглядеть так:
      <?php
      switch ($modx->event->name) {
          case 'OnFileManagerUpload':
              $generator = $modx->newObject('modResource');
              $bases = $source->getBases($directory);
              $fullPath = $bases['pathAbsolute'].ltrim($directory,'/');
              $directory = $source->fileHandler->make($fullPath);
              foreach ($files as $file) {
                  $ext = @pathinfo($file['name'],PATHINFO_EXTENSION);
                  rename($directory->getPath().$file['name'], $directory->getPath() .
                  $generator->cleanAlias($file['name']));
              }
              break;
          default: break;
      }
      return true;
      1. Сергей 14 октября 2015, 18:18 # 0
        Умоляю, сделай на своем сайте возможность добавить в избранное, часто пользуюсь и часто теряю )
        1. Илья Уткин 19 октября 2015, 13:58 # 0
          Ну в шапке есть поиск. На сайте не так уж много статей, чтобы долго искать… Но над избранным я подумаю, хорошо.
          1. Сергей 19 октября 2015, 14:02 # 0
            можно с легкостью забыть то что ищешь)
        2. Alex 28 марта 2017, 17:43(Комментарий был изменён) # 0
          Илья, я тоже в translit, в файле russian.php заменяю точку только на тире ('.'=>'-'). Соответственно файл «Как вести себя при-дтп.jpg» получил название kak-vesti-sebya-pri-dtp-.jpg
          Подскажите, пожалуйста как убрать вот это лишнее тире перед форматом *-.jpg
          1. Alex 28 марта 2017, 19:38 # 0
            Нашел. Сам себе отвечу. )))
            gist.github.com/govza/5136346
      2. Павел 22 марта 2016, 09:00 # 0
        Привет, а подскажи, как ещё помимо этого добавить сегодняшнею дату в конце файла, к примеру грузим файл «Привет.jpg», а получаем «Privet-23-03-2016.jpg»
        1. Илья Уткин 22 марта 2016, 09:14 # 0
          Наверное, где-то здесь просто добавить:
          $generator->cleanAlias($file['name'] . '-' . date('d-m-Y'))
          1. Павел 22 марта 2016, 09:46 # 0
            Если так, то на выходе имя «privet.jpg-2016-03-22»
            1. Илья Уткин 22 марта 2016, 09:52 # 0
              А, ну да… а если так?
              $ext = @pathinfo($file['name'],PATHINFO_EXTENSION);
              $name = str_replace($ext,'',$file['name']) . '-' . date('d-m-Y');
              rename($directory->getPath().$file['name'], $directory->getPath() . $generator->cleanAlias($name));
              1. Павел 22 марта 2016, 09:58 # 0
                Почти, про расширение забыл. Спасибо! Вот полностью плагин, может пригодится кому.

                <?php
                switch ($modx->event->name) {
                    case 'OnFileManagerUpload':
                        $generator = $modx->newObject('modResource');
                        $bases = $source->getBases($directory);
                        $fullPath = $bases['pathAbsolute'].ltrim($directory,'/');
                        $directory = $source->fileHandler->make($fullPath);
                        foreach ($files as $file) {
                            $ext = @pathinfo($file['name'],PATHINFO_EXTENSION);
                            $name = str_replace($ext,'',$file['name']) . '-' . date('d-m-Y').'.'.$ext;
                            rename($directory->getPath().$file['name'], $directory->getPath() .
                            $generator->cleanAlias($name));
                        }
                        break;
                    default: break;
                }
                
                1. Александр 27 октября 2016, 18:20 # 0
                  не силен в php. чет расширение не ставить он все равно))
                  как правильно будет? напишите пожалуйста. Спасибо.
                  1. Александр 27 октября 2016, 19:06 # 0
                    с выводом разобрался, в системных настройках: Шаблон для фильтрации символов в псевдонимах

                    был включен этот символ для вырезания., теперь, после имени он почему то точку вставляет, и получается вот такой вид типа: file.-27-10-2016.jpg
                    1. Александр 27 октября 2016, 19:17 # 0
                      ну теперь нормально вроде выводит

                      <?php
                      switch ($modx->event->name) {
                          case 'OnFileManagerUpload':
                              $generator = $modx->newObject('modResource');
                              $bases = $source->getBases($directory);
                              $fullPath = $bases['pathAbsolute'].ltrim($directory,'/');
                              $directory = $source->fileHandler->make($fullPath);
                              foreach ($files as $file) {
                                  $ext = @pathinfo($file['name'],PATHINFO_EXTENSION);
                                  $name = str_replace('.'.$ext,'',$file['name']) . '-' . date('d-m-Y-H-i-s').'.'.$ext;
                                  rename($directory->getPath().$file['name'], $directory->getPath() .
                                  $generator->cleanAlias($name));
                              }
                              break;
                          default: break;
                      }
                      1. Александр 27 октября 2016, 19:38(Комментарий был изменён) # 0
                        Плагин выше не игнорирует в системных настройках «знак точка»: Шаблон для фильтрации символов в псевдонимах

                        /[\,\$\*\№\;\*\?\»\.\.\«\!\0\(\)\x0B\t\n\r\f\a&=+%#<>"~:`@\?\[\]\{\}\|\^'\\]/

                        поэтому вот модифицированный код.

                        <?php
                        switch ($modx->event->name) {
                            case 'OnFileManagerUpload':
                                $generator = $modx->newObject('modResource');
                                $bases = $source->getBases($directory);
                                $fullPath = $bases['pathAbsolute'].ltrim($directory,'/');
                                $directory = $source->fileHandler->make($fullPath);
                                foreach ($files as $file) {
                                    $ext = @pathinfo($file['name'],PATHINFO_EXTENSION);
                                    rename($directory->getPath().$file['name'], $directory->getPath() .
                                    str_replace($ext, '-' . date('d-m-Y-H-i-s').'.'.$ext, $generator->cleanAlias($file['name'])));
                                }
                                break;
                            default: break;
                        }
                        return true;
                        1. Илья Уткин 27 октября 2016, 19:42 # 0
                          =)))
                2. Илья Уткин 22 марта 2016, 09:53 # 0
                  Кстати, вот здесь есть еще несколько вариантов: community.modx-cms.ru/blog/2478.html
                  1. Павел 14 апреля 2016, 09:43 # 0
                    Привет.
                    Подскажи пожалуйста, если знаешь.

                    В общем, плагин выдаёт такую ошибку если грузить файлы на облако через компонент Swift, может посоветуешь, что-нибудь попробовать?

                    <?php
                    switch ($modx->event->name) {
                        case 'OnFileManagerUpload':
                            $generator = $modx->newObject('modResource');
                            $bases = $source->getBases($directory);
                            $fullPath = $bases['pathAbsolute'].ltrim($directory,'/');
                            $directory = $source->fileHandler->make($fullPath);
                            foreach ($files as $file) {
                                $ext = @pathinfo($file['name'],PATHINFO_EXTENSION);
                                $name = str_replace($ext,'',$file['name']) . '-' . date('d-m-Y').'.'.$ext;
                                rename($directory->getPath().$file['name'], $directory->getPath() .
                                $generator->cleanAlias($name));
                            }
                            break;
                        default: break;
                    }
                    
                    1. Илья Уткин 14 апреля 2016, 11:53 # 0
                      Попробуй вместо функции rename использовать
                      $source->renameObject($oldFile,$this->getProperty('name'));
                      Ну или вообще, использовать процессор browser/file/rename
                      1. Павел 14 апреля 2016, 14:01 # 0
                        С процессорами не знаком, renameObject не отрабатывает.
                        1. Илья Уткин 14 апреля 2016, 14:08 # 0
                          Не знаю, чем еще помочь… Я сам с компонентом Swift еще не работал
                          1. Павел 14 апреля 2016, 14:13 # 0
                            Все равно спасибо! Слушай, а может посоветуешь какой-нибудь хороший компонент для облачного хранилища файлов… мне вот порекомендовали swift с использованием сервиса Selectel…
                            1. Илья Уткин 14 апреля 2016, 14:17 # 0
                              Ну, вот еще есть WebDAV для Яндекс.Диска, например…

                              Но проблема тут не в дополнении, а в «облачности» — просто rename не сработает, так как файлы не на сервере находятся, а в облаке. Поэтому надо облаку передать команду на переименование.
                    2. Александр 27 октября 2016, 18:35 # 0
                      Добрый день. Илья ссылка не работает, и подскажите если не сложно, почему в примере выше не клеится точка к расширению?

                      Получаются файлы типа: file-27-10-2016jpg

                      спасибо.
              2. Раман 12 октября 2016, 15:53 # 0
                Приветствую, Илья.

                Заметил что плагин неправильно понимает букву «й», пишет i с двумя точками сверху. Подскажи пожалуйста как это поправить.
                1. Илья Уткин 12 октября 2016, 16:12 # 0
                  В системной настройке friendly_alias_translit стоит russian?
                  1. Раман 12 октября 2016, 16:22 # 0
                    Да конечно.
                    1. Илья Уткин 12 октября 2016, 16:25 # 0
                      Странно… тогда не знаю, я сам этим плагином давненько пользовался в последний раз
                      1. Раман 12 октября 2016, 16:28 # 0
                        А чем пользуетесь для автоматической транслитерации?
                        1. Илья Уткин 12 октября 2016, 16:29 # 0
                          Для ТВ-шек FastUploadTV — он сам транслитерирует, а для картинок в контенте — ничего не использую, контент-менеджеров заставляю самостоятельно транслитерировать)))
                          1. Раман 12 октября 2016, 16:35 # 0
                            Понял, благодарю.
                            1. Игорь Сидлярук 09 ноября 2017, 17:21(Комментарий был изменён) # 0
                              Здравствуйте. Может подскажете. Суть: при импорте ресурсов (из Excel) и изображений (из zip) нужно, чтобы сработала транслитерация+очистка от начальных/конечных пробелов. Не работает. В ячейках с неправильным написанием названия файла картинки после импорта у ресурса подхватываются картинка верхней в строке ячейки с правильным написанием названия изображения.
                              Импорт — PHPExcel
                              файл импорта — excel
                              В бекенде работает translit нормально
                              — ниже код импорта —
                              <?php
                              // Функции работы со строками
                              require_once 'assets/libs/strings/strings.php';
                              
                              
                              //Массив для ошибок
                              $errors = [];
                              
                              if (isset($_FILES['input-images'])) {
                                  
                                  //Грузим ZIP-файл с картинками, распаковываем в папку /images/
                                  
                                  $upload_dir = './import_temp/';
                                  $upload_zipfile = $upload_dir.basename($_FILES['input-images']['name']);
                                  copy($_FILES['input-images']['tmp_name'], $upload_zipfile);
                                  
                                  $zip = new ZipArchive;
                                  $zip->open($upload_zipfile);
                                  mkdir($_SERVER['DOCUMENT_ROOT'] . "/images/", 0700);
                                  $zip->extractTo($_SERVER['DOCUMENT_ROOT'] . "/images");
                                  $zip->close();
                                      
                                  unlink($upload_zipfile);
                                  
                                  if (isset($_FILES['input-file'])) {
                                      $upload_dir = './import_temp/';
                                      $upload_file = $upload_dir.basename($_FILES['input-file']['name']);
                                          
                                      copy($_FILES['input-file']['tmp_name'], $upload_file);
                                          
                                      require_once 'assets/libs/PHPExcel.php';
                                      $pExcel = new PHPExcel();
                                      
                                      $pExcel = PHPExcel_IOFactory::load($upload_file);
                                      
                                      $Start = 2;
                                      $Res = array();
                                      for ($i= $Start; $i <= 1000; $i++) {
                                          
                                          $Row = new stdClass();
                                          $Row->id = $i;
                                          		
                                          $Row->title = $pExcel->getActiveSheet()->getCell('A'.$i )->getValue(); 
                                          $Row->category = $pExcel->getActiveSheet()->getCell('B'.$i )->getValue(); 
                                          $Row->keyword = $pExcel->getActiveSheet()->getCell('C'.$i )->getValue(); 
                                          $Row->articul = $pExcel->getActiveSheet()->getCell('D'.$i )->getValue(); 
                                          $Row->brand = $pExcel->getActiveSheet()->getCell('E'.$i )->getValue(); 
                                          $Row->picture = $pExcel->getActiveSheet()->getCell('F'.$i )->getValue(); 
                                          $Row->price = $pExcel->getActiveSheet()->getCell('G'.$i )->getValue(); 
                                          $Row->price_old = $pExcel->getActiveSheet()->getCell('H'.$i )->getValue(); 
                                          $Row->non_auto_price_rub = $pExcel->getActiveSheet()->getCell('I'.$i )->getValue(); 
                                          $Row->old_non_auto_rub = $pExcel->getActiveSheet()->getCell('J'.$i )->getValue(); 
                                          $Row->introtext = $pExcel->getActiveSheet()->getCell('K'.$i )->getValue(); 
                                          $Row->content = $pExcel->getActiveSheet()->getCell('L'.$i )->getValue(); 
                                          $Row->stock = $pExcel->getActiveSheet()->getCell('M'.$i )->getValue(); 
                                          $Row->tobest = $pExcel->getActiveSheet()->getCell('N'.$i )->getValue(); 
                                          $Row->description = $pExcel->getActiveSheet()->getCell('O'.$i )->getValue(); 
                                          $Row->tech_char = $pExcel->getActiveSheet()->getCell('P'.$i )->getValue(); 
                                          $Row->garantiya = $pExcel->getActiveSheet()->getCell('Q'.$i )->getValue(); 
                                          $Row->tovar_new = $pExcel->getActiveSheet()->getCell('R'.$i )->getValue(); 
                                          $Row->to_xml = $pExcel->getActiveSheet()->getCell('S'.$i )->getValue(); 
                                          
                                          
                                          if($Row->keyword == null) continue;
                                          		
                                          $Res[] = $Row;
                                          
                                      }
                                      $idx = 0;
                                      $err_row = 2;
                                              
                                      foreach ($Res as $card) {
                                          $title = $card->title; 
                                          $category = $card->category; 	
                                          $keyword = $card->keyword; 
                                          $articul = $card->articul; 
                                          $brand = $card->brand; 
                                          $picture = $card->picture; 
                                          $price = $card->price; 
                                          $price_old = $card->price_old;
                                          $non_auto_price_rub = $card->non_auto_price_rub; 
                                          $old_non_auto_rub = $card->old_non_auto_rub; 
                                          $introtext = $card->introtext; 
                                          $content = $card->content; 
                                          $stock = $card->stock;  
                                          $tobest = $card->tobest; 
                                          $description = $card->description; 
                                          $tech_char = $card->tech_char; 
                                          $garantiya = $card->garantiya; 
                                          $tovar_new = $card->tovar_new; 
                                          $to_xml = $card->to_xml; 
                                              
                                          if (!strpos($picture, '.jpg') && !strpos($picture, '.jpeg') && !strpos($picture, '.png') && !strpos($picture, '.gif')) {
                                              if (file_exists('images/' . $picture . '.jpg')) {
                                                  $picture_tv = 'images/' . $picture . '.jpg';
                                              } else if (file_exists('images/' . $picture . '.png')) {
                                                  $picture_tv = 'images/' . $picture . '.png';
                                              } else if (file_exists('images/' . $picture . '.gif')) {
                                                  $picture_tv = 'images/' . $picture . '.gif';
                                              } else if (file_exists('images/' . $picture . '.jpeg')) {
                                                  $picture_tv = 'images/' . $picture . '.jpeg';
                                              }
                                          } else {
                                              $picture_tv =  'images/' . $picture;
                                          }
                                              
                                          if ($picture_tv != "") {
                                              if ($title != '' && $keyword != '' && $picture != '') {
                                                  $keyword_check = $modx->getObject('modResource',array('link_attributes' => $keyword));
                                                  if ($keyword_check == "") {
                                                      
                                                      $parent = $modx->getObject('modResource', $category);
                                                      $parent->set('class_key', 'CollectionContainer');
                                                      $parent->save();
                                                      
                                                      $newpage = $modx->newObject('modDocument');
                                                      $newpage->set('pagetitle', $title);
                                                      $newpage->set('alias', str2url($title));
                                                      $newpage->set('longtitle', '');
                                                      $newpage->set('parent', $category);
                                                      $newpage->set('published', '1');
                                                      $newpage->set('isfolder', '0');
                                                      $newpage->set('template', '4');
                                                      $newpage->set('content', $content);
                                                      $newpage->set('introtext', $introtext);
                                                      $newpage->set('link_attributes', $keyword);
                                                      $newpage->save();
                                                      $newpage->setTVValue('product_articul', $articul);
                                                      $newpage->setTVValue('product_brand', $brand);
                                                      $newpage->setTVValue('product_img', $picture_tv);
                                                      $newpage->setTVValue('price', $price);
                                                      $newpage->setTVValue('price_old', $price_old);
                                                      $newpage->setTVValue('non_auto_price_rub', $non_auto_price_rub);
                                                      $newpage->setTVValue('old_non_auto_rub', $old_non_auto_rub);
                                                      $newpage->setTVValue('product_stock', $stock);
                                                      $newpage->setTVValue('product_tobest', $tobest);
                                                      $newpage->setTVValue('tab-description', $description);
                                                      $newpage->setTVValue('tab-features', $tech_char);
                                                      $newpage->setTVValue('garantiya', $garantiya);
                                                      $newpage->setTVValue('product_new', $tovar_new);
                                                      $newpage->setTVValue('to_xml', $to_xml);
                                                      
                                                      $idx++;
                                                      
                                                  } else {
                                                      array_push($errors, "
                                                          
                              <strong>Карточка не загружена</strong> → дубликат keyword: " . $keyword . "
                                                          
                              <strong>Карточка:</strong>  " . $title ." / <strong>keyword</strong> карточки: " . $keyword ); 
                                                  }
                                                      
                                              } else {
                                                  array_push($errors, "
                              <strong>Карточка не загружена</strong> → отсутствует назавание picture и/или keyword.
                                                          
                              <strong>Карточка:</strong>  " . $title ." / <strong>keyword</strong> карточки: " . $keyword ); 
                                              }
                                          } else {
                                              array_push($errors, "
                              <strong>Карточка не загружена</strong> → отсутствует изображение или неправильно задано имя файла (" . $picture . ")
                                                  
                              <strong>Карточка:</strong>  " . $title ." / <strong>keyword</strong> карточки: " . $keyword ); 
                                          }
                                              
                                          $err_row++;
                                      }
                                      $modx->cacheManager->refresh();    
                                      echo "<p>Импорт завершён. Импортировано карточек: <b>" . $idx . "</b></p>";
                                          
                                      unlink($upload_file);
                                  } else {
                                      array_push($errors, "Импорт прерван.
                              Отсутствует  файл импорта XLS, или выбран некорректный файл.");
                                  }
                              } else {
                                  array_push($errors, "Импорт прерван.
                              Отсутствует ZIP-архив с изображениями, или выбран некорректный файл.");
                              }
                              if ($errors && $_FILES['input-file']) {
                                  echo "Во время импорта произошли следующие ошибки: ";
                                  echo "<ul>";
                                  foreach ($errors as $err) {
                                      echo "<li>" . $err . "</li>";
                                  }
                                  echo "</ul>";
                              };
                              
                              — strings.php который подключается в самом начале —
                              <?php
                              function rus2translit($string) {
                                  $converter = array(
                                     'а' => 'a',   'б' => 'b',   'в' => 'v',        'г' => 'g',   'д' => 'd',   'е' => 'e',
                                      'ё' => 'e',   'ж' => 'zh',  'з' => 'z',       'и' => 'i',   'й' => 'y',   'к' => 'k',
                                      'л' => 'l',   'м' => 'm',   'н' => 'n',       'о' => 'o',   'п' => 'p',   'р' => 'r',
                                      'с' => 's',   'т' => 't',   'у' => 'u',       'ф' => 'f',   'х' => 'h',   'ц' => 'c',
                                      'ч' => 'ch',  'ш' => 'sh',  'щ' => 'sch',     'ь' => '\'',  'ы' => 'y',   'ъ' => '\'',
                                      'э' => 'e',   'ю' => 'yu',  'я' => 'ya',
                                      'А' => 'A',   'Б' => 'B',   'В' => 'V',        'Г' => 'G',   'Д' => 'D',   'Е' => 'E',
                                      'Ё' => 'E',   'Ж' => 'Zh',  'З' => 'Z',        'И' => 'I',   'Й' => 'Y',   'К' => 'K',
                                      'Л' => 'L',   'М' => 'M',   'Н' => 'N',        'О' => 'O',   'П' => 'P',   'Р' => 'R',
                                      'С' => 'S',   'Т' => 'T',   'У' => 'U',        'Ф' => 'F',   'Х' => 'H',   'Ц' => 'C',
                                      'Ч' => 'Ch',  'Ш' => 'Sh',  'Щ' => 'Sch',      'Ь' => '\'',  'Ы' => 'Y',   'Ъ' => '\'',
                                      'Э' => 'E',   'Ю' => 'Yu',  'Я' => 'Ya',);
                                  return strtr($string, $converter);
                              }
                              function str2url($strin) {
                                  $str=$strin;
                                  $str = rus2translit($str); // переводим в транслит
                                  $str = strtolower($str); // в нижний регистр
                                  $str = preg_replace('~[^-a-z0-9_]+~u', '-', $str); // заменям все ненужное нам на "-"
                                  $str = trim($str, "-"); // удаляем начальные и конечные '-'
                                  return $str;
                              }
                              
                              function rustolower($string) {
                                  $converter = array(
                                      'А' => 'а',   'Б' => 'б',   'В' => 'в',        'Г' => 'г',   'Д' => 'д',   'Е' => 'е',
                                      'Ё' => 'ё',   'Ж' => 'ж',  'З' => 'з',        'И' => 'и',   'Й' => 'й',   'К' => 'к',
                                      'Л' => 'л',   'М' => 'м',   'Н' => 'н',        'О' => 'о',   'П' => 'п',   'Р' => 'р',
                                      'С' => 'с',   'Т' => 'т',   'У' => 'у',        'Ф' => 'ф',   'Х' => 'х',   'Ц' => 'ц',
                                      'Ч' => 'ч',  'Ш' => 'ш',  'Щ' => 'щ',      'Ь' => 'ь',  'Ы' => 'ы',   'Ъ' => 'ъ',
                                      'Э' => 'э',   'Ю' => 'ю',  'Я' => 'я',);
                                      return $string[0].strtr(substr($string, 1), $converter);
                              }
                              
                              ?>
                  2. Salex 12 октября 2016, 18:25 # 0
                    Использую filetranslit уже пару лет. Как то раз на него по любопытству наткнулся. Исходник не смотрел, но работает как надо.

                    Авторизация

                    через сервис Loginza:

                    Подписка или RSS

                    Буду присылать новые статьи — никакого спама



                    Шаблоны MODX

                    1 2 Дальше »

                    Объектная
                    модель
                    MODX