1use strict;
2use warnings;
3
4package Jifty::Plugin;
5use base qw/Class::Accessor::Fast Jifty::Object/;
6__PACKAGE__->mk_accessors('_pre_init');
7
8=head1 NAME
9
10Jifty::Plugin - Describes a plugin to the Jifty framework
11
12=head1 DESCRIPTION
13
14Plugins are like mini-apps.  They come in packages with share
15directories which provide static and template files; they provide
16actions; they have dispatcher rules.  To create the skeleton of a new
17plugin, you can use the command:
18    jifty plugin --name SomePlugin
19
20To use a plugin in your Jifty application, find the C<Plugins:> line
21in the C<config.yml> file:
22
23      Plugins:
24        - SpiffyThing: {}
25        - SomePlugin:
26            arguments: to
27            the: constructor
28
29The dispatcher for a plugin should live in
30C<Jifty::Plugin::I<name>::Dispatcher>; it is written like any other
31L<Jifty::Dispatcher>.  Plugin dispatcher rules are checked before the
32application's rules; however, see L<Jifty::Dispatcher/Plugins and rule
33ordering> for how to manually specify exceptions to this.
34
35Actions and models under a plugin's namespace are automatically
36discovered and made available to applications.
37
38=cut
39
40=head2 new
41
42Sets up a new instance of this plugin.  This is called by L<Jifty>
43after reading the configuration file, and is supplied whatever
44plugin-specific settings were in the config file.  Note that because
45plugins affect Mason's component roots, adding plugins during runtime
46is not supported.
47
48=cut
49
50sub new {
51    my $class = shift;
52    my $self = $class->SUPER::new( { @_ });
53
54    # XXX TODO: Add .po path
55    $self->init(@_);
56
57    Jifty->plugins( Jifty->plugins, $self );
58
59    # Pull in the dispatcher
60    Jifty::Util->require($class->dispatcher);
61
62    return $self;
63}
64
65
66=head2 init [ARGS]
67
68Called by L</new>, this does any custom configuration that the plugin
69might need.  It is passed the same parameters as L</new>, gleaned from
70the configuration file.
71
72=cut
73
74sub init {
75    1;
76}
77
78=head2 new_request
79
80Called right before every request.  By default, does nothing.
81
82=cut
83
84sub new_request {
85}
86
87sub _calculate_share {
88    my $self  = shift;
89    my $class = ref($self);
90
91    return $self->{share} if exists $self->{share};
92
93    my $class_to_path = $class;
94    $class_to_path =~ s|::|/|g;
95    unless ( $self->{share} and -d $self->{share} ) {
96        # If we've got a Jifty in @INC, and the plugin is core, the
97        # right thing to do is to strip off lib/ and replace it with
98        # share/plugins/Jifty/Plugin/Whatever/
99        $self->{share} = $INC{ $class_to_path . '.pm' };
100        $self->{share} =~ s{lib/+\Q$class_to_path.pm}{share/plugins/$class_to_path};
101        $self->{share} = File::Spec->rel2abs( $self->{share} );
102    }
103    unless ( $self->{share} and -d $self->{share} ) {
104        # As above, but only tack on share/, for when we have a
105        # non-core plugin in @INC.  We do this before the
106        # File::ShareDir, because File::ShareDir only looks at install
107        # locations, and the plugin could be hand-set in @INC.
108        $self->{share} = $INC{ $class_to_path . '.pm' };
109        $self->{share} =~ s{lib/+\Q$class_to_path.pm}{share};
110        $self->{share} = File::Spec->rel2abs( $self->{share} );
111    }
112    unless ( $self->{share} and -d $self->{share} ) {
113        # If it's an installed non-core plugin, File::ShareDir's
114        # dist_dir will find it for us
115        my $dist = $class;
116        $dist =~ s/::/-/g;
117        local $@;
118        eval { $self->{share} = File::ShareDir::dist_dir($dist) };
119    }
120    unless ( $self->{share} and -d $self->{share} ) {
121        # We try this last, so plugins that moved out of core, but
122        # were installed at when they _were_ in core, will get the
123        # updated plugin
124
125        # Core plugins live in jifty's share/plugins/Jifty/Plugin/Whatever/
126        $self->{share} = Jifty::Util->share_root;
127        $self->{share} .= "/plugins/" . $class_to_path;
128    }
129    unless ( $self->{share} and -d $self->{share} ) {
130        $self->{share} = undef;
131    }
132    return $self->{share};
133}
134
135
136=head2 template_root
137
138Returns the root of the C<HTML::Mason> template directory for this plugin
139
140=cut
141
142sub template_root {
143    my $self = shift;
144    my $dir =  $self->_calculate_share();
145    return unless $dir;
146    return $dir."/web/templates";
147}
148
149=head2 po_root
150
151Returns the plugin's message catalog directory. Returns undef if it doesn't exist.
152
153=cut
154
155sub po_root {
156    my $self = shift;
157    my $dir = $self->_calculate_share();
158    return unless $dir;
159    return $dir."/po";
160}
161
162=head2 template_class
163
164Returns the Template::Declare view package for this plugin
165
166=cut
167
168sub template_class {
169    my $self = shift;
170    my $class = ref($self) || $self;
171    return $class.'::View';
172}
173
174
175=head2 static_root
176
177Returns the root of the static directory for this plugin
178
179=cut
180
181sub static_root {
182    my $self = shift;
183    my $dir =  $self->_calculate_share();
184    return unless $dir;
185    return $dir."/web/static";
186}
187
188=head2 dispatcher
189
190Returns the classname of the dispatcher class for this plugin
191
192=cut
193
194sub dispatcher {
195    my $self = shift;
196    my $class = ref($self) || $self;
197    return $class."::Dispatcher";
198}
199
200=head2 prereq_plugins
201
202Returns an array of plugin module names that this plugin depends on.
203
204=cut
205
206sub prereq_plugins {
207    return ();
208}
209
210=head2 version
211
212Returns the database version of the plugin. Needs to be bumped any time the database schema needs to be updated. Plugins that do not directly define any models don't need to worry about this.
213
214=cut
215
216sub version {
217    return '0.0.1';
218}
219
220=head2 bootstrapper
221
222Returns the name of the class that can be used to bootstrap the database models. This normally returns the plugin's class name with C<::Bootstrap> added to the end. Plugin bootstrappers can be built in exactly the same way as application bootstraps.
223
224See L<Jifty::Bootstrap>.
225
226=cut
227
228sub bootstrapper {
229    my $self = shift;
230    my $class = ref $self;
231    return $class . '::Bootstrap';
232}
233
234=head2 upgrade_class
235
236Returns the name of the class that can be used to upgrade the database models and schema (such as adding new data, fixing default values, and renaming columns). This normally returns the plugin's class name with C<::Upgrade> added to the end. Plugin upgrade classes can be built in exactly the same was as application upgrade classes.
237
238See L<Jifty::Upgrade>.
239
240=cut
241
242sub upgrade_class {
243    my $self = shift;
244    my $class = ref $self;
245    return $class . '::Upgrade';
246}
247
248=head2 table_prefix
249
250Returns a prefix that will be placed in the front of all table names for plugin models. Be default, the plugin name is converted to an identifier based upon the class name.
251
252=cut
253
254sub table_prefix {
255    my $self = shift;
256    my $class = ref $self;
257    $class =~ s/\W+/_/g;
258    $class .= '_';
259    return lc $class;
260}
261
262=head2 wrap
263
264Takes a PSGI-$app closure and returns the wrapped one if your plugin
265wants to do something to the request handling process.  See also
266L<Plack::Middleware>.
267
268=cut
269
270sub wrap {
271    my ($self, $app) = @_;
272    return $app;
273}
274
275=head2 psgi_app_static
276
277Returns a PSGI-$app that serves the static content of the plugin if
278any.  The default is a <Plack::App::File> app with root set to
279plugin's C<static_root>
280
281=cut
282
283sub psgi_app_static {
284    my $self = shift;
285    my $static_root = $self->static_root;
286    return unless defined $static_root && -d $static_root && -r $static_root;
287    Plack::App::File->new(root => $static_root)->to_app
288}
289
2901;
291