📜 ⬆️ ⬇️

MovableType - excerpts from plugin development

MovableType is not very popular in our country, however, the blogging system is very good. Out of the box it supports a lot of blogs, from the 5th version - also managing the full site. Powered by perl, by generating a variety of static html files, due to which it can withstand even heavy loads.

It is distributed in two versions: OpenSource and Pro. Plugins for it are distributed in a semi-free mode: watch, see, use pay.

In general, the system is written in the spirit of OOP, there are hooks for almost every sneeze, there is an ORM, everything is almost fine ...

Installing plugins


So, the first moment: installing plugins. Installing plugins consists of:

Depending on the functionality and complexity of the plug-in, the last two points sometimes pour out into a non-trivial task. Some plug-in manufacturers even developed special plug-ins to simplify these tasks, such as TemplateInstaller from mt-hacks.com .
Therefore, if you decide to write a plug-in under MT, think carefully about the fact that the user needs to do as little gestures as possible.
')

MT plugin description


So, we decided to give birth to the plugin. Create a folder structure for it:
plugins/coolplugin/
plugins/coolplugin/lib/
plugins/coolplugin/tmpl/
mt-static/plugins/coolplugin/

And thinking: what to write about?

To get started, write config.yaml. Starting with some (4.xxx like) version, the description of the plug-in can be made in a simple and accessible form via the config.yaml file, and not to add a heap of code in the main plug-in file. So, what can and should contain config.yaml?
And no one knows . If you have a desire to find out what can be there - welcome, study $mt/lib/MT/Plugin.pm and other plugins.

The most useful things are:
 name: The name of your plugin.  It is recommended that such a short string fit.
 id: PluginNameWordName
 author_link: Link to plugin author
 author_name: plugin author name
 description: <__ trans phrase = "Description of the plugin, appears if you click on the plugin's name in the plugin menu">
 version: Version of Plugin
 schema_version: Version of the Schema of the Data Plugin
 plugin_link: Link to the page where you can merge the plugin
 doc_link: Link to the page where the description of the plugin
 blog_config_template: name_configuration_configuration.tpl
 l10n_class: Plugin :: ClassInternationalization
 icon: Picture Plugin. {gif / png / jpg / itd}
 settings:
     How to configure:
         default: "default value"
         scope: blog
     global setup:
         default: "value more"

 init: $ Plugin :: Module :: Initialization_Function

 callbacks:
     SomeHack: $ Plugin :: Module :: Hak_function

 tags:
     function:
         Tagname: $ Plugin :: Module :: tag_function
     block:
         Block Tag Name: $ Plugin :: Module :: block_ tag_function

 object_types:
     an object:
         field: type # this field will be added to the object in the database
         field: meta type # this field will be stored in the metadata table and will not require updating the database structure

 applications:
     # about this later, while you forgot


It is unlikely that you will need all the sections at once, but the lack of a meaningful description about what might be there will create many more problems for you.
Above, I described everything that I came across myself. Even such a trifle as the icon: was found in the MT code itself, and not in the documentation.

Plugin setup


Much has been written about customization. MT offers you a choice: create a settings page yourself, or use an autogenerator. If your plugin is configured by 2-3-4 simple lines / checkboxes / so forth, of course, it is much easier and faster to get an auto-oscillator. In general, the article " HOWTO: Plugin's own settings page " from Adept MT Byrne Reese will help you in the basics (Byrne really knows a lot about MT, I advise you to read his posts).

I want to describe a few points related to the configuration, a description of which I could not find anywhere:

Validation of data when configuring the plugin

MT itself nowhere practically produces data validation. Maximum saves after reduction to the necessary type, therefore, it was not so easy to find how to carry out the validation.

So, here you have a plugin class that was configured in the init hook (see above config.yaml). The method itself (for example, lies in plugins / OurPlugin / lib / Plugin.pm) in the simplest case looks like this:
 package OurPlugin::Plugin use base 'MT::Plugin'; sub cb_init { my $p = shift; return bless $p, 'OurPlugin::Plugin'; } 

and is described in config.yaml like this:
init: $OurPlugin::Plugin::cb_init

This means that at the moment of saving, OurPlugin :: Plugin: save_config will be called, to which three arguments will be passed: our object, the form data in the hash, and the scope.

Since we already have base MT :: Plugin, if we don’t do anything, then everything that the user introduced will be saved as-is. If we want to do any validation, we need to:
 sub save_config { my ($plugin, $args, $scope) = @_; my @errors; #           if (@errors) { return $plugin->error("<br />\n".join("<br />\n", @errors)); } return $plugin->SUPER::save_config($args, $scope); } ## end sub save_config 


This will give you a creepy saving error page that will have a back button. Therefore? you can go another way: add another function load_config:
 sub load_config { my ($plugin, $args, $scope) = @_; $plugin->SUPER::load_config($args, $scope); #   -   ,   $plugin->error()   if($plugin->errstr) { # Set $args->{error} to display error banner right before plugin settings $args->{error} = $plugin->errstr; } } ## end sub load_config 

And in the configuration template insert a piece
  <mt:if name="error"> <mtapp:statusmsg id="generic-error" class="error"> <mt:var name="error"> </mtapp:statusmsg> </mt:if> 


Thus, arbitrary data will be saved, but when you open the settings window, the user will see what is wrong there. Which way to go is up to you, you can use both methods for different occasions, it is important to remember that many standard tags do not work inside the plug-in configuration template. That is, they are available, but they give out crap, because the template is compiled in a separate independent context, so there is not even a blog ID for which settings are generated - and here load_config comes to the rescue load_config to set to $args->{...} necessary variables, without which it would not be possible to normally issue any prompts to the user.

Connecting jQuery to customize the plugin

Very merry moment. MT5 uses jQ vovuyu, but it is not in MT4. At the same time, some plugins for MT4 already load jQ, so if we just load jQ all the time, there will be a conflict in our heads.
For myself, I decided this in a piece in blog_config.tmpl:
 <mt:If tag="Version" lt="5"> <script type="text/javascript"> // prevent double-load of jQuery, save if jQ already loaded its state if(window.jQuery) { window.__PLUG_jQ = window.jQuery; window.__PLUGB = window.$; } </script> <script type="text/javascript" src="<$mt:StaticWebPath$>jquery/jquery.js"></script> <script type="text/javascript"> // Restore there jQ state, if it was loaded before if(window.__PLUG_jQ) { window.jQuery = window.__PLUG_jQ; window.$ = window.__PLUGB; window.__PLUG_jQ = undefined; window.__PLUGB = undefined; } </script> </mt:If> 

The method is not very beautiful, but it allows you to be sure that jQ will be exactly loaded, and $ / jQuery will not be spoiled.

Tag Magic


Now consider the tags: functions and blocks.

A tag function is a certain function that receives the current context, arguments, conditions and returns a string, which will be used. In templates, tags are referenced via <$mt:$> .

Classic questions:


A tag block is either a loop, or a condition, or something else, in which the “insides” are defined in the template, but how they are compiled is determined by the block itself. They are described in tags / block in config.yaml (see above). A classic tag loop for an array looks like this:
 sub tag_myloop { my($ctx, $args, $cond) = @_; my $builder = $ctx->stash('builder'); #       . #   ,     -  my $tokens = $ctx->stash('tokens'); #   ,     my $res = ''; my $vars = $ctx->{__stash}{vars} ||= {}; for my $i (0..$#looparray) { my $item = $looparray[$i]; #    ,        local $vars->{__item__} = $item; #     local $vars->{__first__} = $i == 0; local $vars->{__last__} = ($i==($#looparray-1)); local $vars->{__odd__} = ($i % 2) == 0; local $vars->{__even__} = ($i % 2) == 1; local $vars->{__counter__} = $i+1; defined(my $out = $builder->build($ctx, $tokens, $cond)) or return $ctx->error($builder->errstr); $res .= $out; } return $res; } 

Thus, the body will be compiled the necessary number of times, concatenated and issued to the outside. If you wish, of course, you can turn on another logic, another thing is that this will not be obvious enough, so it’s better to follow the general rule: the tag-block provides access to the necessary data, everything else if necessary in other tags and in the template itself.

Callbacks: hacks to the core


In general, MT contains the ability to stick its handlers for almost all occasions. More information about which places are available can be found in the documentation , only there, again, not all are listed, but only the main ones. If you need, for example, to set up any template for any page in the admin panel, then you need to define a hook on MT::App::CMS::template_param. . And if you need to change the output of the page, then template_output is used. These hooks are extremely useful if you want to create your own page in the admin panel (about which, if there is demand, in the next post). When and if you need to find out what kind of hooks are there, feel free to grep the MT code for run_callbacks , and analyze the POD of the corresponding module.

Since the topic of callbacks can be chewed for a long time for all occasions, I will give only the simplest example, such as, for example, automatically deploying syslki on Twitter inside the post.
Defined in config.yaml
  callbacks:
     MT :: Entry :: pre_save: $ OurPlugin :: Plugin :: cb_entry_pre_save
     # cms_pre_save.entry: $ OurPlugin :: Plugin :: cb_cms_pre_save_entry
     # api_pre_save.entry: $ OurPlugin :: Plugin :: cb_api_pre_save_entry 

We have three points where we can stick in to process the text of a record before saving it.


For our task, the entry_pre_save is perfect, since we just need to handle the text according to the regexp. So, we write:
 sub cb_entry_pre_save { my ($cb, $entry, $original) = @_; my $txt = $entry->text; if ($txt ne $original->text) { my $newtxt = $txt; # regexp from twitter-blackbird-pie plugin $newtxt =~ s/([^a-zA-Z0-9_]|^)([@\xef\xbc\xa0]+)([a-zA-Z0-9_]{1,20})(\/[a-zA-Z][a-zA-Z0-9\x80-\xff-]{0,79})?/$1@<a href="http://twitter.com/intent/user?screen_name=$3" class="twitter-action">$3</a>/ug; $entry->text($newtxt) if ($newtxt ne $txt); } return 1; } ## end sub cb_entry_pre_save 


Applications: admin setup


The MT itself is divided into several “separate” parts, each of which starts from its own startpoint. It:


For each part in the config.yaml applications section, you can set your own parameters and settings that will be expanded / replaced by the plugin. The entire section is extremely poorly documented (only in PODs and watch the use itself).
The main use of the section is to announce the ajax calls you need. For example, the ajax method available on a public site:
  applitcations:
     comments:
         methods:
             record_some_info: $ OurPlugin :: Plugin :: ajax_record_some_info 

And the function to it in the plugin:
 sub ajax_record_some_info { my ($app) = @_; my $blog = $app->blog; my $result = "{'error': 'No information supplied'}"; my $user_name = $app->param('user_name'); my $info = $app->param('info'); if ($user_name && $info) { $result = do_store_info($user_name, $info); #     } $app->send_http_header(""); $app->print($result); return $app->{no_print_body} = 1; } ## end sub ajax_record_some_info 

Add something like this to the interface (the code must be generated, or only the path is generated):
 var u = mtGetUser(); if(u && !u.is_anonymous) { jQuery.post({ url: '<mt:CGIPath encode_js='1'><mt:CommentScript encode_js='1'>', '__mode': 'record_some_info', 'user_name': u.name, 'info': 'he he' }); 

Here it is important that the URL for the post should be obtained from the set <mt: CGIPath encode_js = '1'> <mt: CommentScript encode_js = '1'>, as it may differ in each place. The classic solution is to generate a .js file, MT does so, or type something into the Header template.
 <script>COMMENTS_URL = '<mt:CGIPath encode_js='1'><mt:CommentScript encode_js='1'>';</script> 

and use COMMENTS_URL where appropriate.

Missed moments


I did not describe working with the database, how to expand existing ones and how to create new objects, as well as how to create my own pages in the admin panel and i18n questions. Read about it in the next issue.

useful links


Source: https://habr.com/ru/post/119939/


All Articles