MODX - TV(переменные шаблона) изнутри.

Сразу замечу, что я здесь опущу мелкие детали, что бы не потерять "стратегичекую" мысль))). Лишь бы уловить саму логику. Пишу больше для себя, ибо трудно в голове держать все связи. Но думаю, будет интересно и тем, кто хочет всегда все знать глубоко. Здесь я уж касался темы TV(Template Variable). Со временем стал все сильнее меня мучить вопрос: как это все функционирует "изнутри". И вот пока выдалось более-менее свободное время, решил поразбираться. Вообще "погружение" в исходники MODX весьма увлекательное и полезное занятие. Поскольку документации даже на английском весьма мало. Ну надо уяснить что в самом TV - ничего сложного, создается элементарно. Самое интересное в написании своих типов ввода и вывода. Типы ввода - для backend(контекст менеджера). Типы вывода - для отображения на странице(web context) с помощью тэга [[*имя_TV]].

Все что мы видим в менеджере - это виджеты. Т.е. JS код. Реализованный на ExtJS(фреймворк ява-скрипта). И механизмы общения с сервером стандартные:

  • В свойстве "url" - указывается коннектор(скрипт PHP, который вызывается через AJAX)
  • В свойстсве "action" - путь до процессора относительно стандартного каталога процессоров
  • Таким образом коннектор знает, какой процессор вызвать функцией runProcessor. Опосредованно через modRequest.
  • Тут есть один нюанс
    • "Нормальный" процессор возвращает литеральный json. Типа этого:
      {"success":true,"total":"2","results":[{"name":"URL","value":"url"},{"name":"Vologda-2","value":"Vologda-2"}]}
      
      Это годится для получения списков типов ввода и вывода. И не подходит для отрисовки свойств типов TV в менеджерe
    • И вот тут процессоры "симулируют" поведение контроллеров(контроллеры - обеспечивают интерфейс в менеджере)
      • Создают потомка modManagerController
      • вызыват потомок->render()
      • подключают smarty, ищут и парсят TPL(шаблоны для smarty)
      • загружают CSS, JS и прочее, характерное для контроллеров
    • И возвращают не json, а куски HTML(с JS-скриптом(не обязательно)), т.е. готовый интерфейсный объект
Пример параметров, получаемых процессором
$scriptProperties=Array
(
    [action] => element/tv/renders/getProperties
    [context] => mgr
    [tv] => 9
    [type] => image
)
  • А вот все поля TV(тип ввода и вывода - "изображение")
    $tv=Array
    (
        [id] => 9
        [source] => 1
        [property_preprocess] => 
        [type] => image
        [name] => test_botton
        [caption] => 
        [description] => 
        [editor_type] => 0
        [category] => 12
        [locked] => 
        [elements] => 
        [rank] => 0
        [display] => image
        [default_text] => 
        [properties] => Array
            (
            )
    
        [input_properties] => Array
            (
                [allowBlank] => true
                [maxLength] => 
                [minLength] => 
            )
    
        [output_properties] => Array
            (
                [alttext] => 
                [hspace] => 
                [vspace] => 
                [borsize] => 
                [align] => none
                [name] => 
                [class] => as_b
                [id] => 455667
                [style] => color: red;
                [attributes] => v="6"
            )
    
        [static] => 
        [static_file] => 
        [content] => 
    )

    В родной документации обработчики входных типов(на стороне сервера) называются контроллерами.

    Наиболее просто добавить свой контроллер - это поместить его в каталог "..../core/model/modx/processors/element/tv/renders/mgr/input/". И он сразу появится в списке входных типов при создании TV. Вопрос: почему в каталоге процессоров?

    Ответ: потому что виджет списка входных типов использует стандартный коннектор, который подключает стандартные процессоры:

    Вот например для отрисовки TV в менеджере:

    • url: MODx.config.connector_url
    • ..................................................
    • 'action': 'element/tv/renders/getInputProperties'

    Виджеты TV можно найти в каталоге "..../manager/assets/modext/widgets".

    Обработчик на сервере(PHP - файл) на самом деле является процессором (class modTvRendersGetPropertiesProcessor extends modProcessor). Так вот этот процессор делает следующее:

    • устанавливает permission='view_tv'
    • initialize()
      • инициализация smarty(шаблонизатор)
      • определение контекста
      • тип TV устанавливается в "default"
      • return true
    • process()
      • $this->renderController() (здесь и создается реальный контроллер, потомок modManagerController) в итоге в $this->content - "пропаренный" tpl.
        • создается экземпляр TvInputPropertiesManagerController - $this->modx->controller
          • permission='view_tv'
          • public $loadFooter = false; public $loadHeader = false;
          • public function loadCustomCssJs() {}
          • public function process(array $scriptProperties = array()) {}
          • public function getPageTitle() {return '';}
          • public function getTemplateFile() {return 'empty.tpl'; }
        • return $this->modx->controller->render(). Не все строки показаны(там их очень много...)
          • "Доступ запрещен" - если нет permission='view_tv'
          • выброс события OnBeforeManagerPageInit(array('action' => $this->config,))
          • $this->loadControllersPath();
          • $this->loadTemplatesPath();
          • OnManagerPageBeforeRender(array('controller' => &$this));
          • $tpl = $this->getTemplateFile();
          • ... присваиваются различные переменные для smarty
          • $this->content .= $this->getHeader()
          • $content .= $this->fetchTemplate($tpl); - вот тут smarty подключается
          • $this->content .= $this->getFooter()
          • return $this->content;
          - возвращается строка
        шаблоны SMARTY ищет в этом каталоге(+ передаваемый параметр и получает полный путь):
                            
        $this->templatesPaths=Array
        (
            [0] => .........../manager/templates/default/
        )
        
      • $this->getInputProperties()
        • устанавливает плейсхолдеры для smarty, если у TV есть параметры
        • $settings = $tv->get('input_properties');
        • $this->modx->controller->setPlaceholder('params',$settings);
        • return $settings;
      • $renderDirectories = $this->getRenderDirectories()
        • Динамическое определение каталога
          • $renderDirectories = array(dirname(__FILE__).'/'.$this->getProperty('context').'/inputproperties/',);
          • т.е. $renderDirectories = "/core/model/modx/processors/element/tv/renders/mgr/inputproperties/" для нашего случая
        • Подключение каталога пользователя, если тот определен в плагине
          • $pluginResult = $this->modx->invokeEvent('OnTVInputPropertiesList',array('context' => $this->getProperty('context'),));
          • итог например: $pluginResult ="core/components/ourtvs/tv/inputoptions/"
          • $renderDirectories = array_merge($renderDirectories,$pluginResult);
          • т.е. формируется массив путей всех возможных.
        • Цикл по всем nameSpace(из кэша)
          • $inputDir = rtrim($namespace['path'],'/').'/tv/inputproperties/';
          • $renderDirectories[] = $inputDir;
          • т.е. в массив добавляются все пути из namespace(всех пространств имен)
        • return $renderDirectories; - возвращается массив всех возможных каталогов
        Все скрипты для отрисовки входных типов TV будут искаться тут:
        $renderDirectories=Array
        (
            [0] => ......../core/model/modx/processors/element/tv/renders/mgr/inputproperties/
            [1] => ......../core/components/gallery/elements/tv/inputoptions/
            [2] => ......../core/components/ourtvs/tv/inputoptions/
        )                   
      • return $this->getRenderOutput($renderDirectories)
        • Цикл по всем каталогам, определенным в пред. функции
        • $renderFile = найденный каталог+$this->getProperty('type').'.php'; (для типа 'text' - '/core/model/modx/processors/element/tv/renders/mgr/inputproperties/text.php')
        • Если $renderFile существует, цикл прерывается.
        • return include $renderFile;

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

    Ну тут все аналогично.

    • action процессора - 'element/tv/renders/getInputs'
    • все пути оканчиваются на 'input' т.е. все файлы скрипты для отрисовки входных типов TV будут искаться тут:
      $renderDirectories=Array
      (
          [0] => ......../core/model/modx/processors/element/tv/renders/mgr/input/
          [1] => ......../core/components/gallery/elements/tv/input/
          [2] => ......../core/components/ourtvs/tv/input/
      )                   
    • process() тут иной.
      Цикл по всем найденным каталогам:
      • Считываются все файлы в каталоге
      • Из имени каждого найденного файла удаляются подстроки '.php','.class','.class.php'
      • Эти "обрубки"($type) добавляются в ассоциативный массив
      • $types[$type] = array('name' => $this->modx->lexicon($type),'value' => $type, );
      • т.е. можно добавить в лексикон(словарь) для "красивого" отображения
      • asort($types); - сортируется(по значениям)
      • В Цикле по полученному массиву $otypes[] = $type; - т.е. ассоциативный массив верхнего уровня преобразуется в индексный(чтоб скормить его в json)
      • return $this->outputArray($otypes) - возвращается json(литеральный естествеено)

    Таким образом, процессор возвращает:

    {"success":true,"total":"25","results":[{"name":"URL","value":"url"},{"name":"Vologda-2","value":"Vologda-2"},{"name":"galleryalbumlist"....
    

    А вот тут не все аналогично.

    • action процессора - 'element/tv/renders/getOutputs' - 'getOutputs.php' - не содержит в себе класса, а просто скрипт. Т.е. если что, можно не заморачиваться с классами, а сразу вернуть коннектору json. Это очень круто! Круто, да не очень. Дело в том, что в этом случае создается "горбатый"(deprecated) процессор. Так что лучше этим не пользоваться.
    • все пути оканчиваются на 'output'(что логично) т.е. все типы(для вывода в WWW) будут искаться тут
      $renderDirectories=Array
      (
          [0] => .............../core/model/modx/processors/element/tv/renders/web/output/
          [1] => .............../core/components/gallery/elements/tv/output/
          [2] => .............../core/components/ourtvs/tv/output/
      )
                         
    • Поскольку тут нет класса, все сразу в сктрипте.
      Цикл по всем найденным каталогам:
      • Считываются все файлы в каталоге
      • Из имени каждого найденного файла удаляются подстроки '.php','.class','.class.php'
      • Эти "обрубки"($type) добавляются в ассоциативный массив
      • $types[$type] = array('name' => $this->modx->lexicon($type),'value' => $type, );
      • т.е. можно добавить в лексикон(словарь) для "красивого" отображения
      • ksort($types); - сортируется(по ключам, что неожиданно)
      • В Цикле по полученному массиву $otypes[] = $type; - т.е. ассоциативный массив верхнего уровня преобразуется в индексный(чтоб скормить его в json)
      • return $this->outputArray($otypes) - возвращается json(литеральный естествеено)

    Таким образом, процессор возвращает:

    {"success":true,"total":"25","results":[{"name":"URL","value":"url"},{"name":"Vologda-2","value":"Vologda-2"},{"name":"galleryalbumlist"....
    
    • action процессора - 'element/tv/renders/getProperties' - 'getOutputs.php' - не содержит в себе класса, а просто скрипт. Т.е. если что, можно не заморачиваться с классами, а сразу вернуть коннектору json. Это очень круто!
    • все пути оканчиваются на 'output'(что логично) т.е. все типы(для вывода в WWW) будут искаться тут
      $renderDirectories=Array
      (
          [0] => ......../core/model/modx/processors/element/tv/renders/mgr/properties/
          [1] => ......../core/components/gallery/elements/tv/properties/
          [2] => ......../core/components/ourtvs/tv/properties/
                          
    • Здесь вообще мешанина из классов и просто скрипта. Видать к этому времени уж подустали разработчики. Ну в целом аналогично как в самом первом случае (getInputProperties)
      • проверяется permission 'view_tv'
      • определяется контекст(в нашем случае - 'mgr')
      • симуляция поведения контроллера
        • запуск smarty
        • создание потомка modManagerController с пустыми полями и empty.tpl
        • вызов render() из этого потомка
    ... остальное аналогично.

    Здесь как ни странно все без процессоров. По крайней мере на уровне интерфейса. TV стразу встраиваются в страницу на сервере.

    • При открытии страницы - "...manager/index.php"(/manager/?a=resource/update&id=38)
    • handleRequest() (класс modmanagerrequest), тут получают:
      $this->action='resource/update'
      $this->namespace='core'         
           
    • $this->prepareResponse()(метод предка modRequest);
      • $this->modx->getResponse() - подключается modmanagerresponce
      • $this->modx->response->outputContent($options);(modmanagerresponce) в $options - пустой массив
        • подключается класс modmanagercontroller
        • вычисляется имя класса - ResourceUpdateManagerController($this->action+'ManagerController')
        • поскольку такого класса не существует то
          • $classFile='resource/update.class.php'
          • Далее этот файл ищется в каталогах:
            $paths=Array
            (
                [0] => ...../manager/controllers/default/
                [1] => ...../manager/controllers/
            )    
                                          
          • и в итоге находится. $classPath='..../manager/controllers/default/resource/update.class.php' и подключается
          • А если б существовал класс, то просто возвращается имя класса. Оно возвращается в любом случае.
        • Создается экземляр класса контроллера
        • $this->modx->controller->initialize();
        • И наконец - $this->body = $this->modx->controller->render(); тот в частности process(). А в ней есть $this->loadTVs($reloadData). Метод предка 'ResourceManagerController'; наконец-то....
          • в конце концов вызывается renderInput($resource= null, $options = array()) (класс modTemplateVar)
            • $resource - объект ресурса(страницы)
            • $options=Array([value] => )
            • Значение TV опред. по ID ресурса. $value = $this->getValue($resourceId);
            • обработка биндингов
            • $value = $this->checkForFormCustomizationRules($value,$resource);
              • Проверка "Form Customization rules" для данного TV
            • $this->set('value',$value);
            • $this->set('processedValue',$value);
            • $params = $this->get('input_properties'); - считываются входные параметры
            • $inputRenderPaths = $this->getRenderDirectories('OnTVInputRenderList','input'); - очень важная вещь!!! определяются пути где ищутся котроллеры. $subdir='input'
              • + 'processors_path'.'element/tv/renders/'.$context.'/'.'input'.'/' - 'core/model/modx/processors/element/tv/renders/mgr/input/'
              • + 'processors_path'.'element/tv/renders/'.($subdir == 'input' ? 'mgr' : 'web').'/'.$subdir.'/'
              • + пути из плагина на событие 'OnTVInputRenderList' $pluginResult = $this->xpdo->invokeEvent($event,array('context' => $context,));
                $pluginResult=Array
                (
                    [0] => /home/aset/data/www/cppv.fvds.ru/mdx/core/components/gallery/elements/tv/input/
                    [1] => /home/aset/data/www/cppv.fvds.ru/mdx/core/components/ourtvs/tv/input/
                )                                              
                                                           
              • каждая проверяется на рельность
              • каждая проверяется на существование файлов($file->isReadable() && $file->isFile())
              • отбрасываются "дубли".
            • return $this->getRender($params,$value,$inputRenderPaths,'input',$resourceId,$this->get('type'));
              • параметры объявленные ($params,$value,array $paths,$method,$resourceId = 0,$type = 'text')
              • определяется класс для отрисовки. Зарегистрированный или нет.
              • $renderFile = $path.$type.'.class.php'; - например '..../core/components/myTVs/input/obvl.class.php', т.е. $type='obvl'
              • $output = $render->render($value,$params);
              • если $output пустой, то подключается 'core/model/modx/processors/element/tv/renders/mgr/input/text.class.php'
              • возвращается $output

    Да. Все очень сурово... Это сколько нужно времени и мозгов...Хотя в официальной документации и рекомендуют использовать пространство имен(NS), вместо "плагинов путей", но тут видно, что при формировании массива путей контроллеров для TV(input-render), про NS явно забыли. По крайней мере в моей версии(2.3).

    Вызываются сразу 2 процессора:

    • Action - 'element/propertyset/updatefromelement'. - тут сохраняются наборы параметров(propertyset). Нам пока это не интересно.
    • Action - 'element/tv/update'. Вот тут похоже сохраняется сам TV со всеми свойствами(параметрами).
      • beforeSave()
        • $this->setInputProperties()
          • отбрасывается из ключей подстрока 'inopt_'. В виджете все поля входных свойств начинаются с 'inopt_'(аттрибут name). Получаем(в нашем случае):
                                            
            $inputProperties=Array
            (
            )
            
        • $this->setOutputProperties();
          • отбрасывается из ключей подстрока 'prop_'. В виджете все поля выходных свойств начинаются с 'prop_'(аттрибут name). Получаем:
            $outputProperties=Array
            (
                [alttext] => Рисунок
                [hspace] => 10
                [vspace] => 11
                [borsize] => 12
                [align] => middle
                [name] => pic
                [class] => as_b
                [id] => 455667
                [style] => color: red;
                [attributes] => v="6"
            )
          • Т.е. сохраняются массивы. Но в базе хранятся как литеральный json. Это благодаря типу поля в модели -
            <field key="input_properties" dbtype="text" phptype="array" null="true"/>

    Для справки: доступные поля($fields) в процессоре(на примере TV типа img)
    $this->getProperties()=Array
    (
        [action] => element/tv/update
        [templates] => {}
        [resource_groups] => {}
        [sources] => [{"context_key":"web","source":"1","name":"Filesystem","menu":null}]
        [propdata] => []
        [id] => 9
        [props] => 
        [name] => test_botton
        [caption] => 
        [description] => 
        [static_file] => 
        [category] => 12
        [rank] => 0
        [locked] => 0
        [clearCache] => 1
        [static] => 0
        [source] => 1
        [propertyset] => По умолчанию
        [property_preprocess] => 0
        [type] => image
        [els] => 
        [default_text] => 
        [display] => image
        [prop_alttext] => Рисунок
        [prop_hspace] => 10
        [prop_vspace] => 11
        [prop_borsize] => 12
        [prop_align] => middle
        [prop_name] => pic
        [prop_class] => as_b
        [prop_id] => 455667
        [prop_style] => color: red;
        [prop_attributes] => v="6"
        [ext-comp-1062] => 20
        [ext-comp-1079] => 20
    )
    

    В отличие от всех предыдущих случаев, здесь инициатором служит не виджет(через коннектор), а парсер MODX. Например, для отображения TV типа img запускается скрипт по пути:

       'core/model/modx/processors/element/tv/renders/web/output/image.class.php' 
    
    Там описан класс - потомок modTemplateVarOutputRender(потомок - modTemplateVarRender).
    • process($value,array $params = array()) - вызывается из modTemplateVarRender->render()
      • $value=assets/resurs/denegNetDergytes/2.png
      • $params=
        Array
        (
            [alttext] => Кому на руси жить хорошо. фото Медведев Д.
            [hspace] => 10
            [vspace] => 11
            [borsize] => 12
            [align] => middle
            [name] => pic
            [class] => as_b
            [id] => 455667
            [style] => color: red;
            [attributes] => v="6"
        )
                        
        т.е. и есть свойства вывода. Кто-то уже заботливо считал json из базы и преобразовал в массив(ассоциативный).
      • $images=$this->tv->parseInput($value, '||', 'array'); - т.е. может быть несколько картинок, разделенных '||'
      • далее просто генерится HTML для массива из img со всеми атрибутами

    Вот теперь многое прояснилось. Причем не только в TV. Но и в логике всей системы. Обработка входных и выходных параметров для TV сделана очень хорошо. На мой взгляд. Жаль что в "modTemplateVarResource" поле "value" имеет phptype="string". Если б было "array" было бы легче с "комплексными" TV(когда TV имеет много полей). Хотя может это мало кому требуется. В принципе и так хорошо. Пол года назад, прочитав все это, я б наверное в душе обложил матюгами разработчиков))). Но теперь понимаю что это очень круто. Не перестает поражать продуманность и гибкость. Т.е. одно и то же, можно сделать несколькими путями. Например, можно воспользоваться стандартными каталогами для контроллеров TV. А можно определить свои. Причем опять же двумя методами. Через пространство имен, и через плагин. Ладно, это все хорошо. Теперь попробую уже на другом уровне познания создать TV со своими типами ввода, вывода и отображением в WWW так как умные люди это делают .


  • Комментарии 0






    Разрешённые теги: <b><i><br>Добавить новый комментарий: