Исправляем ошибку меню для ссылок из url_rewrite.php. Хак.

1635
Доброго всем времени суток!
Есть проблема, я описывал ее здесь Проблема активного меню с custom_rewrite
Вкратце:
создаем в меню пункт Вход, ставим тип "ссылка", сама ссылка вот такая /login.
Теперь у нас по клику по пункту меню Вход - можно зайти на сайт.
Кликаем по ней...
Но пункт меню ВХОД не стал активным!
Налицо ошибка в разборе меню, т.е. ссылки вида /login, /passremind b прочие не обрабатываются!
Вы скажете да зачем нам это нужно, у нас эти ссылки висят отдельно. Да, на самом деле, смысла вносить изменения ради этих ссылок нет никакого.
Смысл есть, если мы будем использовать custom_rewrite для своих низменных важных целей. Например, сделать так, чтобы урл контента в каком-либо компоненте был не пятнадцать слов а два

Итак поехали.

сегодня правим файл cms.php

добавляем в начале файла строку
Код PHP:
  1. private $rules;
там закешируем правила rewrite

берем функцию loadMenuStruct (загрузка меню в память)
меняем цикл
Код PHP:
  1. while ($item = $inDB->fetch_assoc($result)){
  2. $this->menu_struct[$item['id']] = $item;
  3. }
на
Код PHP:
  1. while ($item = $inDB->fetch_assoc($result)){
  2. $this->menu_struct[$item['id']] = $item;
  3. $realink='/'.$this->detectorMenuURI($item['link']);
  4. $this->menu_struct[$item['id']]['realink'] = $realink;
  5. }
это мы посчитали "настоящую" ссылку в меню

добавляем новую функцию -
Код PHP:
  1. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  2.  
  3. private function detectorMenuURI($muri){
  4.  
  5. $uri = $muri;
  6. $uri = ltrim($uri, '/');
  7. $rules = array();
  8.  
  9. $folder = rtrim($uri, '/');
  10.  
  11. if (in_array($folder, array('admin', 'install', 'migrate'))) { return; }
  12. if (!$this->rules) {
  13. if(file_exists(PATH.'/url_rewrite.php')) {
  14. //подключаем список rewrite-правил
  15. $this->includeFile('url_rewrite.php');
  16. if(function_exists('rewrite_rules')){
  17. //получаем правила
  18. $rules = rewrite_rules();
  19. }
  20. }
  21.  
  22. if(file_exists(PATH.'/custom_rewrite.php')) {
  23. //подключаем список пользовательских rewrite-правил
  24. $this->includeFile('custom_rewrite.php');
  25. if(function_exists('custom_rewrite_rules')){
  26. //добавляем к полученным ранее правилам пользовательские
  27. $rules = array_merge($rules, custom_rewrite_rules());
  28. }
  29. }
  30. if ($rules){ $this->rules=$rules;}
  31. }
  32. else {
  33. $rules=$this->rules;
  34. }
  35.  
  36. $found = false;
  37.  
  38. if ($rules){
  39.  
  40. //перебираем правила
  41. foreach($rules as $rule_id=>$rule) {
  42.  
  43. //небольшая валидация правила
  44. if (!$rule['source'] || !$rule['target'] || !$rule['action']) { continue; }
  45. //проверяем совпадение выражения source с текущим uri
  46. if (preg_match($rule['source'], $uri, $matches)){
  47. //перебираем совпавшие сегменты и добавляем их в target
  48. //чтобы сохранить параметры из $uri в новом адресе
  49. foreach($matches as $key=>$value){
  50. if (!$key) { continue; }
  51. if (strstr($rule['target'], '{'.$key.'}')){
  52. $rule['target'] = str_replace('{'.$key.'}', $value, $rule['target']);
  53. }
  54. }
  55.  
  56. //действие по-умолчанию: rewrite
  57. if (!$rule['action']) { $rule['action'] = 'rewrite'; }
  58.  
  59. //выполняем действие
  60. switch($rule['action']){
  61. case 'rewrite' : $uri = $rule['target']; $found = true; break;
  62. case 'redirect' : $this->redirect($rule['target']); break;
  63. case 'redirect-301' : $this->redirect($rule['target'], '301'); break;
  64. case 'alias' : $this->includeFile($rule['target']); $this->halt();break;
  65. }
  66.  
  67. }
  68.  
  69. if ($found) { break; }
  70.  
  71. }
  72. }
  73.  
  74. return $uri;
  75.  
  76. }
  77.  
  78. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  79.  
А теперь в функции menuID() меняем цикл foreach
Код PHP:
  1. foreach($menu as $item){
  2.  
  3. if (!$item['link']) { continue; }
  4.  
  5. //полное совпадение ссылки и адреса?
  6. if ($uri == $item['link']){
  7. $menuid = $item['id'];
  8. $is_strict = true; //полное совпадение
  9. break;
  10. }
  11. //частичное совпадение ссылки и адреса (по началу строки)?
  12. $uri_first_part = substr($uri, 0, strlen($item['link']));
  13. if ($uri_first_part == $item['link']){
  14. $menuid = $item['id'];
  15. break;
  16. }
  17. }
на

Код PHP:
  1. foreach($menu as $item){
  2.  
  3. if (!$item['link']) { continue; }
  4.  
  5. //полное совпадение ссылки и адреса?
  6. if ($uri == $item['link']){
  7. $menuid = $item['id'];
  8. $is_strict = true; //полное совпадение
  9. break;
  10. }
  11. //////////////// Определяем правильную ссылку для пункта меню
  12. if ($uri == $item['realink']){
  13. $menuid = $item['id'];
  14. $is_strict = true; //полное совпадение
  15. break;
  16. }
  17. /////////////////////////////////////////////////////////////////////////////
  18. //частичное совпадение ссылки и адреса (по началу строки)?
  19. $uri_first_part = substr($uri, 0, strlen($item['link']));
  20. if ($uri_first_part == $item['link']){
  21. $menuid = $item['id'];
  22. break;
  23. }
  24. //////////////// Определяем правильную ссылку для пункта меню /////////////
  25. //частичное совпадение ссылки и адреса (по началу строки)?
  26. $uri_first_part = substr($uri, 0, strlen($item['realink']));
  27. if ($uri_first_part == $item['realink']){
  28. $menuid = $item['id'];
  29. break;
  30. }
  31. //////////////////////////////////////////////////////////////////////////////
  32. }
теперь меню работает правильно
делалось под 1.8 для 1.9 думаю будет аналогично
решение не претендует на красоту, но общая идея понятна
а по поводу "зачем все это нужно" я расскажу в следующих постах
Замена sendmail для Denwer | Компонент Инвайтер v.1.5 stable
Комментарии (18)
universe 4 июля 2012 в 21:30 0
"Например, сделать так, чтобы урл контента в каком-либо компоненте был не пятнадцать слов а два"
не понял про что это=)
Крот 4 июля 2012 в 21:35 +1
здесь очень много желающих чтобы ссылка до какого-либо объекта в каком-либо компоненте была не
http://сайт.ру/категория1/категория2/категория3/.../объект.html
а просто
http://сайт.ру/категория/объект.html вне зависимости от вложенности
это просто пример, через custom_rewrite много интересного сделать можно
universe 4 июля 2012 в 22:07 0
тоесть что то общего в нем есть с http://instantcms.ru/blogs/moi-blog-360/komponent-i-plagin-seo-pages.html ?
Крот 4 июля 2012 в 23:02 0
не могу сказать, я еще не смотрел SEO компонент, может просто совпало так))
Крот 4 июля 2012 в 23:26 0
но подозреваю, что есть smile
Алексей Тимофеев 4 июля 2012 в 22:07 -3
Спасибо крот...
Крот 5 июля 2012 в 11:19 0
поправил функцию loadMenuStruct - была небольшая ошибка... если кто поставил, проверьте
Tarhun 11 июля 2012 в 22:57 0
на 1.9 не работает...
Tarhun 12 июля 2012 в 15:29 0
как отредактировать под 1.9?
После пункта "добавляем новую функцию"
Перестает вообще открываться сайт, происходит редирект - http://сайт.ру/сайт.ру/
BSB 16 июля 2012 в 13:53 0
Хм, я в растерянности... Поставил апач на голую убунту 12.04, поправил Override None на Override All в конфиге, убедился, что установлен mod_rewrite - у меня icms 1.9.1 по ссылке на /login вываливается с ошибкой 404.
Есть, правда, вероятность, что версию неполноценную выкачал (критерий был - utf-8). Кто-нить, дайте сцыль на .htaccess правильный? А то у меня такое впечатление, что в транках он левый.
Крот 17 июля 2012 в 00:25 0
для 1.9 надо
функция detectorMenuURI() - на самом деле это просто подправленная копия detectURI - сделано для ускорения работы
можно использовать оригинальный detectURI - но тогда при каждом клике будет пересчитываться список uri
соотв-но при изменении списка в custom_rewrite нужно как-то сбрасывать закэшированное значение

Код PHP:
 private function detectorMenuURI($muri){

     $uri    = $muri;
     $uri    = ltrim($uri, '/');
     $rules  = array();

     $folder = rtrim($uri, '/');

if (strstr($uri, "?") && !preg_match('/^admin\/(.*)/i', $uri) && !strstr($uri, 'go/url=')){
            $query_str = substr($uri, strpos($uri, "?")+1);
            $uri = substr($uri, 0, strpos($uri, "?"));
            parse_str($query_str, $temp_request);
			$_REQUEST = array_merge($_REQUEST, $temp_request);
        }

        if (in_array($folder, array('admin', 'install', 'migrate', 'index.php'))) { return; }
     
        //специальный хак для поиска по сайту, для совместимости со старыми шаблонами
        if (strstr($_SERVER['QUERY_STRING'], 'view=search')){ $uri = 'search'; }

if (!$this->rules) {
       if(file_exists(PATH.'/url_rewrite.php')) {
           //подключаем список rewrite-правил
            self::includeFile('url_rewrite.php');
            if(function_exists('rewrite_rules')){
                //получаем правила
                $rules = rewrite_rules();
            }
        }

        if(file_exists(PATH.'/custom_rewrite.php')) {
            //подключаем список пользовательских rewrite-правил
            self::includeFile('custom_rewrite.php');
            if(function_exists('custom_rewrite_rules')){
                //добавляем к полученным ранее правилам пользовательские
                $rules = array_merge($rules, custom_rewrite_rules());
            }
        }
        if ($rules){  $this->rules=$rules;}
     }
     else {
       $rules=$this->rules;
     }

     $found = false;

     if ($rules){

         //перебираем правила
         foreach($rules as $rule_id=>$rule) {

             //небольшая валидация правила
             if (!$rule['source'] || !$rule['target'] || !$rule['action']) { continue; }
             //проверяем совпадение выражения source с текущим uri
             if (preg_match($rule['source'], $uri, $matches)){
                 //перебираем совпавшие сегменты и добавляем их в target
                 //чтобы сохранить параметры из $uri в новом адресе
                 foreach($matches as $key=>$value){
                     if (!$key) { continue; }
                     if (strstr($rule['target'], '{'.$key.'}')){
                         $rule['target'] = str_replace('{'.$key.'}', $value, $rule['target']);
                     }
                 }

                 //действие по-умолчанию: rewrite
                 if (!$rule['action']) { $rule['action'] = 'rewrite'; }

                 //выполняем действие
                 switch($rule['action']){
                     case 'rewrite'      : $uri = $rule['target']; $found = true; break;
                     case 'redirect'     : $this->redirect($rule['target']); break;
                     case 'redirect-301' : $this->redirect($rule['target'], '301'); break;
                        case 'alias'        : self::includeFile($rule['target']); $this->halt();break;
                 }

             }

             if ($found) { break; }

         }
     }

     return $uri;
     
 }
menuID
Код PHP:
    public function menuId(){

        //если menu_id был определен ранее, то вернем и выйдем
        if ($this->menu_id) { return $this->menu_id; }

        $view       = self::request('view', 'str', '');
        
        if ($this->is_content){
            $uri = substr($this->uri, strlen('content/'));
        } else {
            $uri = $this->uri;
        }

        $uri        = '/'.$uri;

        //флаг, показывающий было совпадение URI и ссылки пунта меню
        //полным или частичным
        $is_strict  = false;

        //главная страница?
        $menuid     = ($uri == '/' ? 1 : 0);
        if ($menuid == 1) {
            $this->is_menu_id_strict = 1;
            return $menuid;
        }

        //перевернем массив меню чтобы перебирать от последнего пункта к первому
        $menu = array_reverse($this->menu_struct);

        //перебираем меню в поисках текущего пункта
        foreach($menu as $item){

            if (!$item['link']) { continue; }

			// uri с учетом имени хоста
			$full_uri = HOST . $uri;

            //полное совпадение ссылки и адреса?
            if ($uri == $item['link'] || $full_uri == $item['link']){
                $menuid = $item['id'];
                $is_strict = true; //полное совпадение
                break;
            }

////////////////  Определяем правильную ссылку для пункта меню
            if ($uri == $item['realink']){
                $menuid = $item['id'];
                $is_strict = true; //полное совпадение
                break;
            }

            //частичное совпадение ссылки и адреса (по началу строки)?
            $uri_first_part = substr($uri, 0, strlen($item['link']));
            if ($uri_first_part == $item['link']){
                $menuid = $item['id'];
                break;
            }
////////////////  Определяем правильную ссылку для пункта меню  /////////////
            //частичное совпадение ссылки и адреса (по началу строки)?
            $uri_first_part = substr($uri, 0, strlen($item['realink']));
            if ($uri_first_part == $item['realink']){
                $menuid = $item['id'];
                break;
            }
           
        }

        $this->menu_id              = $menuid;
        $this->is_menu_id_strict    = $is_strict;

        return $menuid;

    }
сравните со своим cms.php
Fuze 26 июля 2012 в 09:38 +1
Более простое решение, файл core.php

После
Код PHP:
  1. private $uri;
вставляем
Код PHP:
  1. private $real_uri;
Далее в методе detectURI() перед
Код PHP:
  1.  
  2. if ($rules){
  3. //перебираем правила
Вставляем
Код PHP:
  1. $this->real_uri = $uri;
Далее в методе menuId() после
Код PHP:
  1. $uri = '/'.$uri;
вставляем
Код PHP:
  1. $real_uri = '/'.$this->real_uri;
И далее условия по полному или частичному совпадению меняем на
Код PHP:
  1. //полное совпадение ссылки и адреса?
  2. if (in_array($item['link'], array($uri, $full_uri, $real_uri))){
  3. $menuid = $item['id'];
  4. $is_strict = true; //полное совпадение
  5. break;
  6. }
  7.  
  8. //частичное совпадение ссылки и адреса (по началу строки)?
  9. $uri_first_part = mb_substr($uri, 0, mb_strlen($item['link']));
  10. $real_uri_first_part = mb_substr($real_uri, 0, mb_strlen($item['link']));
  11. if (in_array($item['link'], array($uri_first_part, $real_uri_first_part))){
  12. $menuid = $item['id'];
  13. break;
  14. }
Крот 27 июля 2012 в 22:51 0
посмотрел, да так проще
НО:
второе решение, также как и мое содержит один нюанс -
есть два пункта меню
1) /catalog/1001
2) /catalog

при клике на первое - активным становится второе. решается сначала перебором полных путей, а затем, если не найдено перебор частичных путей.
вот теперь меня метод menuID() устраивает полностью )))

Код PHP:
  1. /**
  2.   * Возвращает ID текущего пункта меню
  3.   * @return int
  4.   */
  5. public function menuId(){
  6.  
  7. //если menu_id был определен ранее, то вернем и выйдем
  8. if ($this->menu_id) { return $this->menu_id; }
  9.  
  10. $view = self::request('view', 'str', '');
  11.  
  12. if ($this->is_content){
  13. $uri = substr($this->uri, strlen('content/'));
  14. } else {
  15. $uri = $this->uri;
  16. }
  17.  
  18. $uri = '/'.$uri;
  19.  
  20. $real_uri = '/'.$this->real_uri;
  21. //флаг, показывающий было совпадение URI и ссылки пунта меню
  22. //полным или частичным
  23. $is_strict = false;
  24.  
  25. //главная страница?
  26. $menuid = ($uri == '/' ? 1 : 0);
  27. if ($menuid == 1) {
  28. $this->is_menu_id_strict = 1;
  29. return $menuid;
  30. }
  31.  
  32. //перевернем массив меню чтобы перебирать от последнего пункта к первому
  33. $menu = array_reverse($this->menu_struct);
  34. $fnd=false;
  35. //перебираем меню в поисках текущего пункта
  36. foreach($menu as $item){
  37.  
  38. if (!$item['link']) { continue; }
  39.  
  40. // uri с учетом имени хоста
  41. $full_uri = HOST . $uri;
  42.  
  43. if (in_array($item['link'], array($uri, $full_uri, $real_uri))){
  44. $menuid = $item['id'];
  45. $is_strict = true; //полное совпадение
  46. $fnd=true;
  47. break;
  48. }
  49. }
  50. //перебираем меню в поисках текущего пункта
  51. if (!$fnd)
  52. foreach($menu as $item){
  53.  
  54. if (!$item['link']) { continue; }
  55.  
  56. // uri с учетом имени хоста
  57. $full_uri = HOST . $uri;
  58.  
  59. //частичное совпадение ссылки и адреса (по началу строки)?
  60. $uri_first_part = mb_substr($uri, 0, mb_strlen($item['link']));
  61. $real_uri_first_part = mb_substr($real_uri, 0, mb_strlen($item['link']));
  62. if (in_array($item['link'], array($uri_first_part, $real_uri_first_part))){
  63. $menuid = $item['id'];
  64. break;
  65. }
  66. }
  67.  
  68. $this->menu_id = $menuid;
  69. $this->is_menu_id_strict = $is_strict;
  70.  
  71. return $menuid;
  72.  
  73. }
Tarhun 6 августа 2012 в 01:22 0
Firefox определил, что сервер перенаправляет запрос на этот адрес таким образом, что он никогда не завершится.

Можете скинуть сам файл cms.php для 1.9?
Олег Васильевич я 22 августа 2012 в 12:01 0
И я был бы весьма благодарен за этот файл.
Спасибо!
Bubble Gumoff 15 октября 2012 в 11:20 +1
А как можно без rewrite разбить seolink, чтобы формировались ссылки так
ссылка статьи была host/statya
ссылка категории host/kategoriya
ссылка подкатегории host/podkategoriya
*wildbeez* 25 октября 2012 в 05:06 0
пишу в надежде, что ответят
Крот 6 ноября 2012 в 00:46 0
в 1.10 ошибки больше нет)