Revision 196
Date:
2012/03/15 18:28:32
Author:
ahitrov
Revision Log:
Users plugin
Files:
Legend:
Added
Removed
Modified
utf8/plugins/users/comps/contenido/users/autohandler
1
<%init>
2
3
$r->content_type('text/html');
4
$m->call_next();
5
6
</%init>
utf8/plugins/users/comps/contenido/users/components/__section_tree__.msn
1
%# vim:syn=mason:
2
% users_index_tree( $sect, 0, \$line, $level, $mode, $profile, $width_limit, $root );
3
<%once>
4
5
use Encode;
6
7
</%once>
8
<%args>
9
10
$root => 1
11
$level => 3
12
$mode => 1
13
$width_limit => undef
14
15
</%args>
16
<%init>
17
my $profile = $m->comp('/contenido/components/context.msn', name => 'profile');
18
19
sub users_index_tree
20
{
21
my ($sect, $offset, $line_ref, $count_offset, $viewmode, $profile, $width_limit, $root) = @_;
22
my $section_access = $user->section_accesses($user, $sect->{id});
23
24
my $spacer = '';
25
for(my $c=1; $c<$offset; $c++)
26
{
27
$spacer = $spacer.(($c == $offset-1) ? ' » ' : ' ');
28
}
29
if( $sect->{id} && ($sect->{id} != 1) && ($offset != 0) && $section_access > 0)
30
{
31
$$line_ref++;
32
33
my $href = 'sections.html?id='.$sect->id;
34
35
my $sname = decode("utf-8", $sect->name());
36
if ( defined $width_limit && $offset + length($sname) > $width_limit ) {
37
$sname = substr($sname, 0, $width_limit - $offset);
38
$sname .= ' ...';
39
}
40
$sname = encode("utf-8", $sname);
41
my $statstyle = $sect->contenido_status_style ? ' style="' . $sect->contenido_status_style . '"' : '';
42
my @properties = $sect->structure();
43
my ($statprop) = grep { $_->{attr} eq 'status' } @properties;
44
my ($statcase) = grep { $_->[0] == $sect->status } @{$statprop->{cases}} if exists $statprop->{cases} && ref $statprop->{cases} eq 'ARRAY';
45
my $statname = $statcase->[1] if ref $statcase;
46
my $html_sect = '<span'.($statstyle).(!$sect->status ? ' class="hiddensect"' : '').'>'.($section_access ? '<a href="'.$href.'"'.$statstyle.'>' : '').$sname.($section_access ? '</a>' : '') . ' '.($sect->status != 1 && $statname ? ' <span style="font-size:11px;">('.$statname.')</span>' : '').'</span>';
47
my $style = ($offset == 1) ? ($viewmode ? ' style="font-size:110%;"':' style="font-size:95%;"') : '';
48
49
$m->out(<<EOT);
50
<tr>
51
<td align="right"> ${$line_ref} <a href="section.html?id=$sect->{id}&move=up&ret=$root"><img src="/contenido/i/ico-up-9x10.gif" border=0 alt="Переместить секцию на шаг вверх"></a> <a href="section.html?id=$sect->{id}&move=down&ret=$root"><img src="/contenido/i/ico-down-9x10.gif" border=0 alt="Переместить секцию на шаг вниз"></a></td>
52
<td><table cellpadding="0" cellspacing="0" border="0">
53
<tr valign="top">
54
<td width="10"> </td>
55
<td>$spacer</td>
56
<td nowrap $style>$html_sect</td>
57
</tr>
58
</table>
59
</td>
60
EOT
61
62
if ($viewmode)
63
{
64
my $fhref = '/contenido/?set_context=filter-'.$sect->id();
65
$m->out(qq^\n<td align="center" nowrap><a href="$fhref">уст.фильтр</a>^);
66
$m->out(qq^ <a href="document.html?sect_id=$sect->{id}">доб.докум</a>^) if $section_access == 2;
67
$m->out("</td>");
68
}
69
70
$m->out('</tr>');
71
}
72
73
my $childs = $sect->{childs} || [];
74
if( ref($childs) && @$childs && $offset < $count_offset )
75
{
76
$offset++;
77
foreach my $child (@$childs)
78
{
79
next if ( $child->class() ne 'users::Section' );
80
next if (! $request->{cCLASSES}->{$child->class()});
81
users_index_tree( $child, $offset, $line_ref, $count_offset, $viewmode, $profile, $width_limit, $root );
82
}
83
$offset--;
84
}
85
}
86
87
my $sect = $keeper->get_tree(light=>1, root=>$root);
88
return undef unless ref $sect;
89
90
$user->get_accesses();
91
92
my $line = 0;
93
$request->{cCLASSES} = {};
94
$request->{cCLASSES}->{'users::Section'} = 1;
95
if (ref($request->{tab}->{sections}))
96
{
97
map { $request->{cCLASSES}->{$_} = 1 } (@{ $request->{tab}->{sections} });
98
}
99
return undef if (scalar(keys(%{ $request->{cCLASSES} })) == 0);
100
101
</%init>
utf8/plugins/users/comps/contenido/users/components/find_user.msn
1
<script type="text/javascript">
2
<!--
3
function StartSearch () {
4
var oForm = document.forms['user_search'];
5
var oFormStart = document.forms['start_search'];
6
% my $i = 0;
7
% foreach my $field ( @fields ) {
8
<% $i++ ? '} else if' : 'if' %> ( oForm.<% $field %>.value ) {
9
oFormStart.search.value = oForm.<% $field %>.value;
10
oFormStart.search_by.value = oForm.<% $field %>.name;
11
% }
12
% if ($i) {
13
}
14
% }
15
if ( oFormStart.search.value && oFormStart.search_by.value ) {
16
oFormStart.submit();
17
}
18
}
19
//-->
20
</script>
21
<fieldset>
22
<legend>Поиск пользователя</legend>
23
<form id="start_search" name="start_search" action="<% $url %>" method="get">
24
<input type="hidden" name="search" value="">
25
<input type="hidden" name="search_by" value="">
26
</form>
27
<form id="user_search" name="user_search" action="<% $url %>" method="get">
28
<table cellspacing="2" cellpadding="0" border="0" class="tform">
29
<tr><td height="3"></td></tr>
30
% foreach my $field ( @fields ) {
31
<tr><td align="right"><b><% $props{$field} %>: </b></td>
32
<td><input type="text" name="<% $field %>" size="40" value="<% $search_by && $search_by eq $field ? $search : '' %>"></td></tr>
33
% }
34
35
<tr><td align="right"> </td>
36
<td align="right"><input type="button" size="40" value="Искать" onclick="StartSearch();">
37
<a href="<% $url %>">сбросить фильтр</a>
38
</td></tr>
39
<tr><td height="5"></td></tr>
40
</table>
41
</form>
42
</fieldset>
43
<%args>
44
45
$url => 'index.html'
46
$search => undef
47
$search_by => undef
48
49
</%args>
50
<%init>
51
52
my @fields;
53
my @properties;
54
my %props;
55
my $class = $state->{users}->profile_document_class;
56
my $object = $class->new ($keeper);
57
if ( ref $object ) {
58
@fields = $object->search_fields;
59
@fields = qw( login email nickname name ) unless @fields;
60
@properties = $object->structure();
61
%props = map { $_->{attr} => $_->{rusname} } @properties;
62
} else {
63
@fields = qw( login email nickname name );
64
%props = (
65
'login' => { 'Логин' },
66
'email' => { 'E-mail' },
67
'nickname' => { 'Ник' },
68
'name' => { 'Ф.И.О.' },
69
);
70
}
71
72
</%init>
utf8/plugins/users/comps/contenido/users/components/subsection.msn
1
<fieldset>
2
<legend>Подразделы</legend>
3
4
<table width="100%" border="0" cellpadding="3" cellspacing="0" class="tlistdocs">
5
<tr bgcolor="#efefef">
6
<th align="center" width="1%">N</th>
7
<th>Название</th>
8
9
<& "/contenido/users/components/__section_tree__.msn", root=>$section->id, level=>3, mode=>0, width_limit => 30 &>
10
11
</table>
12
% if ($section_access == 2)
13
% {
14
<div style="font-size:70%;font-family:Tahoma;margin:5px 4px 10px 4px;"><b><a href="section.html?sect_id=<% $section->id %>">Создать подраздел »</a></b></div>
15
% }
16
<div style="font-size:70%;font-family:Tahoma;margin:5px 4px 10px 4px;"><b><a href="./?set_context=filter-<% $section->id %>">Установить в качестве фильтра »</a></b></div>
17
</fieldset>
18
19
20
<%ARGS>
21
22
$section => undef
23
24
</%ARGS>
25
<%INIT>
26
return undef if (! ref($section));
27
my $section_access = $user->section_accesses($user, $section->id);
28
</%INIT>
utf8/plugins/users/comps/contenido/users/components/tasks.msn
1
<%init>
2
3
return (
4
{
5
'attr' => 'structure',
6
'rusname' => 'Редактирование параметров сайта',
7
'component' => 'structure.msn',
8
},
9
{
10
'attr' => 'project',
11
'rusname' => 'Редактирование параметров объектов',
12
'component' => 'project.msn',
13
},
14
{
15
'attr' => 'finder',
16
'rusname' => 'Отбор документов',
17
'component' => 'finder.msn',
18
},
19
{
20
'attr' => 'users',
21
'rusname' => 'Список пользователей системы',
22
'component' => 'users.msn',
23
},
24
{
25
'attr' => 'cache',
26
'rusname' => 'Очистить кэш сайта',
27
'component' => 'cache.msn',
28
},
29
);
30
31
</%init>
utf8/plugins/users/comps/contenido/users/dhandler
1
%#<pre><% Dumper ($m->dhandler_arg) %></pre>
2
<& $call, %ARGS &>
3
<%init>
4
5
my $call;
6
if ( $m->dhandler_arg =~ /section.html/ ) {
7
$call = '/contenido/section.html';
8
} elsif ( $m->dhandler_arg =~ /sections.html/ ) {
9
$call = 'index.html';
10
} elsif ( $m->dhandler_arg =~ /document.html/ ) {
11
$call = '/contenido/document.html';
12
} elsif ( $m->dhandler_arg =~ /confirm.html/ ) {
13
$call = '/contenido/confirm.html';
14
} elsif ( $m->dhandler_arg =~ /document_links.html/ ) {
15
$call = '/contenido/document_links.html';
16
} elsif ( $r->uri eq '/contenido/users/' ) {
17
$call = '/contenido/users/index.html';
18
} else {
19
&abort404;
20
}
21
22
</%init>
utf8/plugins/users/comps/contenido/users/document.html
1
%# vim:syn=mason
2
<& "/contenido/components/header.msn" &>
3
<& "/contenido/components/naviline.msn", sect_id => $owner->id &>
4
5
% if ($error) {
6
<div align="center" style="font-size:110%; color:red;">
7
<% $error %>
8
</div>
9
<br><br>
10
% }
11
12
% if (!ref($document)) {
13
% if ($id) {
14
<div align="center" style="font-size:110%; color:red;">
15
Документ с идентификатором <% $id %> не найден
16
</div>
17
<br><br>
18
% } elsif ($class) {
19
<!-- Блок с выбором нового документа для создания -->
20
<table width="50%" border="0"><tr><td>
21
<fieldset>
22
<legend>Выберите тип документа для создания</legend>
23
<& "/contenido/components/new_objects_form.msn", proto => 'documents', sect_id => $owner->id &>
24
</fieldset>
25
</td></tr></table>
26
% } else {
27
<div align="center" style="font-size:110%; color:red;">
28
Неверный вызов документа!!! (отсутствуют id и class одновременно)
29
</div>
30
<br><br>
31
% }
32
33
% } else {
34
35
<& "/contenido/components/obj_list_js.msn", object => $document &>
36
<& "/contenido/components/object_form.msn",
37
object => $document,
38
proto => 'documents',
39
sect_id => $owner->id,
40
clone => $clone,
41
filter_params => \%filter_params,
42
&>
43
% if (ref($document) && ($document->id))
44
% {
45
46
<!-- Связи и привязки к рубрикам -->
47
48
<br>
49
50
<table width="100%" cellspacing="5" cellpadding="0" border="0">
51
<tr>
52
<td width=50% valign=top>\
53
<& "/contenido/components/document_sections.msn", document => $document &>\
54
<& "/contenido/components/user_sections.msn", luser => $document &>\
55
% if ( $m->comp_exists ("/contenido/components/pbase_rubrics.msn") ) {
56
<& "/contenido/components/pbase_rubrics.msn", document => $document &>\
57
% }
58
</td>
59
<td width=50% valign=top>\
60
% if ( $m->comp_exists ("/contenido/components/pbase_links.msn") ) {
61
<& "/contenido/components/pbase_links.msn", document => $document &>\
62
% }
63
<iframe id="links" src="/contenido/document_links.html?id=<% $document->id() %>&class=<% $document->class() %>" width="100%" height="700" frameborder="0"></iframe>
64
65
</td>
66
</tr>
67
</table>
68
69
<!-- / Связи и привязки к рубрикам -->
70
71
% }
72
% }
73
74
</body>
75
</html>
76
<%ARGS>
77
$p => 1
78
$class => undef
79
$sect_id => undef
80
$s_alias => undef
81
$id => undef
82
$delete => undef
83
$save => undef
84
$clone => undef
85
$activate => undef
86
$deactivate => undef
87
</%ARGS>
88
<%INIT>
89
90
&abort404 unless $class;
91
my $error='';
92
### !!! При добавлении переменных в ARGS их надо внести в список исключений в структуре ниже
93
94
my $filter = $m->comp('/contenido/components/context.msn', name => 'filter');
95
96
my $document;
97
my $new;
98
99
if ($id && ($id !~ /\D/) && ($id > 0)) {
100
$document = $keeper->get_document_by_id($id, class=>$class);
101
if ( $clone && exists $document->{'attributes'}->{'dtime'} ) {
102
$document->dtime(undef);
103
}
104
} elsif ( ($class) && (length($class)>0) && (! ref($document)) ) {
105
$document = new $class ($keeper);
106
$new = 1;
107
my @properties = $document->structure();
108
foreach my $prop ( @properties ) {
109
my $attr = $prop->{attr};
110
### !!! Если не стандартная переменная, то можем инициализировать
111
if ( exists $ARGS{$attr} && ! grep { $prop->{attr} eq $_ } qw( class sect_id id delete save clone s_alias activate deactivate ) ) {
112
$document->$attr($ARGS{$attr});
113
}
114
}
115
}
116
&abort404 unless ref $document;
117
118
my @props = $document->structure();
119
my %filter_params;
120
if ($ARGS{use_section} && !grep { $_->{attr} eq 'use_section' } @props ) {
121
$filter_params{use_section} = $ARGS{use_section};
122
$filter_params{class} = $document->class;
123
}
124
$filter_params{alpha} = $ARGS{alpha} if $ARGS{alpha} && !grep { $_->{attr} eq 'alpha' } @props;
125
$filter_params{alpha_search} = $ARGS{alpha_search} if $ARGS{alpha_search} && !grep { $_->{attr} eq 'alpha_search' } @props;
126
$filter_params{search_by} = $ARGS{search_by} if $ARGS{search_by} && !grep { $_->{attr} eq 'search_by' } @props;
127
$filter_params{search} = $ARGS{search} if $ARGS{search} && !grep { $_->{attr} eq 'search' } @props;
128
$filter_params{s} = $ARGS{s} if $ARGS{s} && !grep { $_->{attr} eq 's' } @props;
129
$filter_params{p} = $p if $p > 1;
130
my $return_params = join ('&', map { $_.'='.$filter_params{$_} } grep { $_ ne 's' } keys %filter_params );
131
132
133
if ($s_alias) {
134
$sect_id = $project->s_alias->{$s_alias};
135
}
136
137
if ( (! $sect_id) && (ref($document)) && ($document->id) ) {
138
$sect_id = $document->section();
139
}
140
my $owner = $keeper->get_section_by_id ($sect_id || $Contenido::Section::ROOT || 1);
141
142
if (! ref($owner)) {
143
$owner = $keeper->get_section_by_id ($Contenido::Section::ROOT || 1);
144
}
145
if (! ref($owner)) {
146
warn "Contenido Die: Не могу найти корневую секцию\n";
147
return undef;
148
}
149
150
if (ref($document) && $document->id() && $document->section()) {
151
my $document_access = $user->section_accesses($user, $document->section);
152
if ($document_access != 2) {
153
$m->clear_buffer;
154
$m->abort(403);
155
}
156
}
157
158
# Удаление...
159
if (defined($id) && ($id > 0) && ($delete == 1)) {
160
$document->delete( attachments => 1 );
161
162
$m->redirect("sections.html?id=".($filter_params{s} || $owner->id).($return_params ? '&'.$return_params : ''));
163
}
164
165
# Активация...
166
if (defined($id) && ($id > 0) && ($activate == 1)) {
167
$document->status(1);
168
$document->store;
169
170
$m->redirect("sections.html?id=".($filter_params{s} || $owner->id).($return_params ? '&'.$return_params : ''));
171
}
172
173
# Дективация...
174
if (defined($id) && ($id > 0) && ($deactivate == 1)) {
175
$document->status(0);
176
$document->store;
177
178
$m->redirect("sections.html?id=".($filter_params{s} || $owner->id).($return_params ? '&'.$return_params : ''));
179
}
180
181
# Сохранение существующего документа или создание нового...
182
# Кстати, пока никак не обрабатываются связи...
183
elsif ( $save == 1 )
184
{
185
my $clonesource;
186
if ( $clone ) {
187
$clonesource = $keeper->get_document_by_id ($clone,
188
class => $document->class,
189
);
190
}
191
if ($m->comp('/contenido/components/set_properties.msn', object => $document, SETS => \%ARGS) != 1)
192
{
193
# Ошибка, надо бы обработать...
194
warn "Contenido Warning: Не могу установить значения полей!\n";
195
}
196
if ( $clone ) {
197
$m->comp('/contenido/components/clone_attachments.msn', object => $document, source => $clonesource );
198
$document->sections( $clonesource->sections );
199
} elsif ( $new ) {
200
$document->sections( $owner->id, $filter > 0 ? ($filter) : ());
201
}
202
203
204
unless ($document->store()) {
205
# Ошибка, надо бы обработать...
206
$error="Ошибка сохранения ($keeper->{last_error})";
207
} else {
208
209
if ($ARGS{_save_and_leave}) {
210
$m->redirect("sections.html?id=".($filter_params{s} || $owner->id).($return_params ? '&'.$return_params : ''));
211
} elsif ($ARGS{_save_and_again}) {
212
$m->redirect("document.html?class=".$document->class()."§_id=".($filter_params{s} || $owner->id).($return_params ? '&'.$return_params : ''));
213
}
214
215
216
$m->redirect("document.html?id=".$document->id()."&class=".$document->class().(exists $filter_params{s} ? '&s='.$filter_params{s} : '').($return_params ? '&'.$return_params : ''));
217
}
218
}
219
220
</%INIT>
utf8/plugins/users/comps/contenido/users/document_filter_list.html
1
<& "/contenido/components/title.msn" &>
2
<div style="font:bold 13px Verdana; margin:4px;">Класс: <span style="color:teal;"><% $class %></span>
3
<span style="font:11px Verdana;">(<a href="document.html?class=<% $class %>&<% $field %>=<% $id %>" target="_blank">создать новый</a>)</span></div>
4
% if ( ref $docs eq 'ARRAY' && @$docs ) {
5
% if ($total > $size) {
6
<div style="font-size:75%; font-family:Arial; text-align:center;">
7
<& /inc/pages_.msn, p => $p, n => $size, total => $total, params => {%ARGS} &></div>
8
% }
9
% my @props = sort { $a->{column} <=> $b->{column} } grep { exists $_->{column} } $docs->[0]->required_properties;
10
<table width="100%" border="0" cellpadding="4" cellspacing="0" class="tlistdocs">
11
<tr bgcolor="#efefef">
12
% foreach my $prop (@props) {
13
<th><% $prop->{rusname} %></th>
14
% }
15
</tr>
16
% foreach my $doc (@$docs) {
17
% my $color = $doc->contenido_status_style ? $doc->contenido_status_style :
18
% $doc->status == 1 ? 'blue' :
19
% $doc->status == 0 ? 'gray' : 'blue';
20
21
%# my ($stat) = grep { $_->{attr} eq 'status' } @props;
22
%# my ($case) = grep { $_->[0] == $banner->status } @{ $stat->{cases} } if ref $stat eq 'HASH' && exists $stat->{cases};
23
<tr>
24
% foreach my $prop (@props) {
25
% my $attr = $prop->{attr};
26
% if ( $attr =~ /dtime/ ) {
27
<td><a href="document.html?id=<% $doc->id %>&class=<% $doc->class %>"
28
style="<% $color %>" target="_blank"><& "/contenido/components/show_dtime.msn", dtime => $doc->$attr &></a></td>
29
% } elsif ( $prop->{type} eq 'status' ) {
30
% my ($case) = grep { $_->[0] == $doc->$attr } @{$prop->{cases}};
31
<td><% ref $case ? $case->[1] : 'Статус: '.$prop->$attr %></td>
32
% } else {
33
<td><a href="document.html?id=<% $doc->id %>&class=<% $doc->class %>"
34
style="<% $color %>" target="_blank"><% $doc->$attr %></a></td>
35
% }
36
% }
37
</tr>
38
% }
39
</table>
40
% }
41
%#<pre><% Dumper($docs->[0]->required_properties) %></pre>
42
<%args>
43
$id => undef
44
$filter => undef
45
$field => undef
46
$class => undef
47
$order_by => undef
48
$p => 1
49
</%args>
50
<%init>
51
52
my ($docs, $total);
53
my $size = 15;
54
if ( $id && $filter && $class ) {
55
my %opts = ();
56
$opts{$filter} = $id;
57
$opts{order_by} = $order_by if $order_by;
58
$docs = $keeper->get_documents (
59
class => $class,
60
return_mode => 'array_ref',
61
offset => ($p-1)*$size,
62
limit => $size,
63
%opts,
64
);
65
$total = $keeper->get_documents (
66
class => $class,
67
count => 1,
68
%opts,
69
);
70
} else {
71
return;
72
}
73
74
</%init>
utf8/plugins/users/comps/contenido/users/index.html
1
<& "/contenido/components/header.msn" &>
2
<& "/contenido/components/naviline.msn", sect_id => $owner->id &>
3
4
<table width="100%" cellspacing="0" cellpadding="0" border="0">
5
<tr valign="top">
6
<td width="35%">
7
8
<& /contenido/users/components/find_user.msn,
9
search => $search, search_by => $search_by,
10
url=>'sections.html' &>
11
12
<& "/contenido/users/components/subsection.msn", section => $owner &>
13
<& "/contenido/components/class_filter.msn", class=> $class, section => $owner &>
14
15
% if (( $owner->id ) && ($owner->id > 0) && $owner->id != 1 )
16
% {
17
<& "/contenido/components/section_info.msn", section => $owner &>
18
% }
19
20
</td>
21
<td width="2%"> </td>
22
<td width="65%">
23
24
% if($owner->id) {
25
26
<fieldset>
27
<legend>Документы\
28
% if ($class) {
29
класса <% $class %>\
30
% if ($use_section) {
31
c учетом текущей секции\
32
% }
33
% } else {
34
в разделе\
35
% }
36
% if ($total > 0) {
37
(всего: <% $total %>, показано с <% $first + 1 %> по <% ($first + $n > $total) ? $total : $first + $n %>)
38
% }
39
</legend>
40
41
% if ($total or defined($alpha) or defined($search) ) {
42
% if ($section_access == 2) {
43
<& "/contenido/components/new_objects_form.msn", proto => 'documents',
44
sect_id => $owner->id,
45
default => ($owner->default_document_class ? $owner->default_document_class : $class) &>
46
% }
47
<div style="font-size:75%; font-family:Arial;">
48
<table border="0" cellspacing="0" cellpadding="2" width="100%" style="margin:4px 0 0; border:1px solid gray;">
49
<tr bgcolor="#e0e0e0"><th colspan="4">Поиск по букве: [<a href="?id=<% $id %>" style="font-weight:normal;">сброс фильтра</a>]</th></tr>
50
<tr><td style="font-size:75%; font-family:Arial; padding:2px 4px;">
51
<& /inc/alpha.msn, alpha=>$alpha, params=>\%ARGS, &>
52
</td></tr></table>
53
% ## Форма поиска. Работает при включенном фильтре класса
54
% ## и описанной для класса функции search_fields
55
% ########################################################
56
% if ( $filter{class} ) {
57
% my $document = $filter{class}->new ($keeper);
58
% my @fields = $document->search_fields;
59
% if ( @fields ) {
60
% my @props = $document->required_properties;
61
<form action="sections.html">
62
<table border="0" cellspacing="0" cellpadding="2" width="100%" style="margin:4px 0; border:1px solid gray;">
63
<tr bgcolor="#e0e0e0"><th colspan="4">Поиск по полю: [<a href="?id=<% $id %>" style="font-weight:normal;">сброс фильтра</a>]</th></tr><tr>
64
<td width="1%" nowrap style="padding:2px 0 2px 4px;"><select name="search_by">
65
% foreach my $field ( @fields ) {
66
% my ($prop) = grep { $_->{'attr'} eq $field } @props;
67
% my $selected = $field eq $search_by ? ' selected' : '';
68
<option value="<% $field %>"<% $selected %>><% ref $prop ? $prop->{'rusname'} : $field %>
69
% }
70
</td><td width="0">:</td>
71
<td width="98%"><input type="text" name="search" value="<% $search %>" style="width:100%"></td>
72
<td width="1%" style="padding:2px 4px 2px 0;"><input type="submit" value=" » " style="border:1px solid gray;"></td>
73
</tr></table>
74
% if ( $id ) {
75
<input type="hidden" name="id" value="<% $id %>">
76
% }
77
% if ( $class ) {
78
<input type="hidden" name="class" value="<% $class %>">
79
% }
80
% if ( $use_section ) {
81
<input type="hidden" name="use_section" value="<% $use_section %>">
82
% }
83
</form>
84
% }
85
% }
86
87
<div style="height:5px"><spacer type="block" height="5"></div>
88
<& /inc/pages_.msn, p=>$p, n=>$n, total=>$total, href=>'sections.html', params=>\%ARGS, &>
89
</div>
90
91
<& /contenido/components/section_browse.msn, documents => \@documents, columns => \@columns, section => $owner, filter => \%filter_params, %ARGS &>
92
93
<div style="font-size:75%; font-family:Arial;">
94
<& /inc/pages_.msn, p=>$p, n=>$n, total=>$total, href=>'sections.html', params=>\%ARGS, &>
95
<div style="height:5px"><spacer type="block" height="5"></div>
96
</div>
97
98
% } else {
99
<h4 align="center"><i>---- Нет документов -----</i></h4>
100
% }
101
%
102
% if ($section_access == 2) {
103
<& "/contenido/components/new_objects_form.msn", proto => 'documents',
104
sect_id => $owner->id,
105
default => ($owner->default_document_class ? $owner->default_document_class : $class) &>
106
% }
107
108
</fieldset>
109
</td>
110
</tr>
111
</table>
112
113
% }
114
115
</body>
116
</html>
117
<%args>
118
119
$id => 1
120
$p => 1
121
$class => undef
122
$use_section => undef
123
$alpha => undef
124
$alpha_search => undef
125
$search_by => undef
126
$search => undef
127
$update => undef
128
$delete => undef
129
130
</%args>
131
<%init>
132
133
$id = 0 if $id =~ /\D/;
134
my $owner;
135
136
# Операции...
137
if ($id && ($id > 0))
138
{
139
$owner = $keeper->get_section_by_id($id);
140
}
141
if (! ref($owner))
142
{
143
return undef;
144
}
145
146
$class = $state->{users}->profile_document_class;
147
$use_section = 1 if $id != 1;
148
149
my %filter_params;
150
$filter_params{use_section} = $use_section if $use_section;
151
$filter_params{class} = $class if $class;
152
$filter_params{alpha} = $alpha if $alpha;
153
$filter_params{alpha_search} = $alpha_search if $alpha_search;
154
$filter_params{search_by} = $search_by if $search_by;
155
$filter_params{search} = $search if $search;
156
$filter_params{p} = $p if $p > 1;
157
$filter_params{s} = $id if $id && $id != 1;
158
159
# Фильтры работают в любом случае...
160
my $filter = $m->comp('/contenido/components/context.msn', name => 'filter');
161
my $profile = $m->comp('/contenido/components/context.msn', name => 'profile');
162
163
unless (defined $request->{section_accesses}->{$id})
164
{
165
$request->{section_accesses}->{$id} = $user->get_section_access($id);
166
}
167
my $section_access = $request->{section_accesses}->{$id};
168
169
my (@documents, $total);
170
171
my $s = $owner->id;
172
my $sorted = $owner->_sorted();
173
$s .= ",$filter" if ($filter > 0);
174
175
if ($update) {
176
my $return_params = join ('&', map { $_.'='.$filter_params{$_} } grep { $_ ne 's' } keys %filter_params );
177
my %updated;
178
while ( my ($field, $value) = each %ARGS ) {
179
if ( $field =~ /^update_(\d+)_(\w+)$/ ) {
180
my $oid = $1;
181
my $attr = $2;
182
$updated{$oid}{$attr} = $value;
183
}
184
}
185
my %classes = map { $_->{class} => 1 } values %updated;
186
foreach my $update_class ( keys %classes ) {
187
my @ids;
188
while ( my ($oid, $attr) = each %updated) {
189
push @ids, $oid if $attr->{class} eq $update_class;
190
}
191
my @objects = $keeper->get_documents (
192
id => \@ids,
193
class => $update_class
194
) if @ids;
195
foreach my $object ( @objects ) {
196
my $document_access = $user->section_accesses($user, $object->section);
197
next unless $document_access == 2;
198
my $fields = $updated{$object->id};
199
my @props = grep { exists $_->{inline} && $_->{inline} } $object->structure;
200
if ( ref $fields eq 'HASH' ) {
201
foreach my $prop ( @props ) {
202
my $attr = $prop->{attr};
203
my $value = ref $fields && exists $fields->{$attr} ? $fields->{$attr} : undef;
204
if ( $prop->{db_type} eq 'float' ) {
205
for ( $value ) {
206
s/\,/\./;
207
s/^\s+//;
208
s/\s+$//;
209
}
210
}
211
if ( $prop->{type} eq 'checkbox' ) {
212
$value = $value ? 1 : undef;
213
}
214
215
$object->$attr($value);
216
}
217
$object->store;
218
}
219
}
220
}
221
$m->redirect("sections.html?id=".$id.($return_params ? '&'.$return_params : ''));
222
}
223
if ( $delete ) {
224
my $return_params = join ('&', map { $_.'='.$filter_params{$_} } grep { $_ ne 's' } keys %filter_params );
225
my %deleted;
226
while ( my ($field, $value) = each %ARGS ) {
227
if ( $field =~ /^delete_(\d+)_(\w+)$/ ) {
228
my $oid = $1;
229
my $attr = $2;
230
$deleted{$oid}{$attr} = $value;
231
}
232
}
233
my %classes = map { $_->{class} => 1 } values %deleted;
234
foreach my $delete_class ( keys %classes ) {
235
my @ids;
236
while ( my ($oid, $attr) = each %deleted) {
237
push @ids, $oid if exists $attr->{id} && $attr->{id} && ($attr->{class} eq $delete_class);
238
}
239
my @objects = $keeper->get_documents (
240
id => \@ids,
241
class => $delete_class
242
) if @ids;
243
foreach my $object ( @objects ) {
244
my $document_access = $user->section_accesses($user, $object->section);
245
next unless $document_access == 2;
246
$object->delete;
247
}
248
}
249
$m->redirect("sections.html?id=".$id.($return_params ? '&'.$return_params : ''));
250
}
251
252
my %filter=();
253
my %order = (not $class and $owner->order_by) ? (order_by => $owner->order_by) : (order => ['date','direct']);
254
if (defined $alpha and $alpha ne '') {
255
$filter{ilike}=1;
256
$filter{ $alpha_search || 'name' }="$alpha%";
257
$order{order}=['name','reverse'];
258
delete $order{order_by};
259
}
260
261
$filter{class} = $owner->default_document_class if $owner->default_document_class;
262
$filter{class} = $class if ($class);
263
$filter{s}=$s unless ($class && !$use_section);
264
if ( $search_by && defined $search ) {
265
my $doc_class = $class || $owner->default_document_class;
266
if ( $doc_class ) {
267
my @props = $doc_class->new( $keeper )->structure();
268
my ($prop) = grep { $_->{attr} eq $search_by } @props if @props;
269
if ( ref $prop && $prop->{type} eq 'integer' ) {
270
$filter{$search_by} = int($search);
271
} else {
272
$filter{$search_by}='%'.$search.'%';
273
$filter{ilike} = 1;
274
}
275
} else {
276
$filter{$search_by}='%'.$search.'%';
277
$filter{ilike} = 1;
278
}
279
}
280
281
# Дополнительные фильтры раздела
282
if ($owner->filters) {
283
no strict 'vars';
284
my $filters = eval($owner->filters);
285
if ($@) {
286
warn "Bad filter: " . $owner->filters . " in section " . $owner->id;
287
} elsif (ref $filters eq 'HASH') {
288
map { $filter{$_} = $filters->{$_} } keys %$filters;
289
}
290
}
291
292
$total = $keeper->get_documents(%filter, count=>1);
293
294
my $n = 40;
295
my $first = $n * ($p - 1);
296
($first,$p)=(0,0) if ($first>$total);
297
298
if ($class && !$use_section) {
299
@documents = $keeper->get_documents(%filter, %order, limit=>$n, offset=>$first);
300
} elsif ($sorted) {
301
@documents = $keeper->get_sorted_documents(%filter, limit=>$n, offset=>$first);
302
} else {
303
@documents = $keeper->get_documents(%filter, %order, limit=>$n, offset=>$first);
304
}
305
306
# набор колонок таблицы документов...
307
my @columns = $sorted ? ({attr => '_sort_', name => 'N'}) : ();
308
309
# пытаемся найти колонки, которые документ сам пожелал показать (required_properties -> column)...
310
if ($filter{class} or @documents and $documents[0]) {
311
push @columns,
312
sort {$a->{column} <=> $b->{column}}
313
grep {$_->{column}} ($filter{class} ? $filter{class}->new($keeper)->structure : $documents[0]->structure);
314
}
315
316
# стандартная жопка таблицы...
317
@columns = (@columns,
318
{attr => '_act_', rusname => 'Действия'},
319
);
320
</%init>
utf8/plugins/users/comps/contenido/users/link_frame.html
1
<frameset border="0" bordercolor="black" rows="40,*" frameborder="1">
2
<frame src="links/title.html?class=<% $class %>" bordercolor="black" marginheight="2" marginwidth="2"
3
name="linktitle" noresize scrolling="no" height="30">
4
<frameset border="0" bordercolor="black" cols="50%,50%" frameborder="1">
5
<frame src="links/source.html<% $sargs %>" bordercolor="gray" marginheight="2" marginwidth="2" name="sourcefrm" noresize scrolling="yes">
6
<frame src="links/destination.html<% $dargs %>" bordercolor="black" marginheight="2" marginwidth="2" name="destfrm" noresize scrolling="yes">
7
</frameset>
8
</frameset>
9
10
<%args>
11
$class => ''
12
$source_class => ''
13
$source_id => ''
14
$dest_class => ''
15
$dest_id => ''
16
$save => 0
17
$status => 0
18
</%args>
19
<%init>
20
21
my (@source_args, $sargs, @dest_args, $dargs);
22
push @source_args, 'class='.$class if $class;
23
push @source_args, 'source_class='.$source_class if $source_class;
24
push @source_args, 'source_id='.$source_id if $source_id;
25
push @dest_args, 'class='.$class if $class;
26
push @dest_args, 'dest_class='.$dest_class if $dest_class;
27
push @dest_args, 'dest_id='.$dest_id if $dest_id;
28
29
$sargs = @source_args ? '?'.join('&', @source_args) : undef;
30
$dargs = @dest_args ? '?'.join('&', @dest_args) : undef;
31
32
</%init>
utf8/plugins/users/comps/contenido/users/links/destination.html
1
<& "/contenido/components/title.msn", title=>'Создание связей' &>
2
<style>
3
<!--
4
body {margin:5px;}
5
-->
6
</style>
7
8
9
% if (ref $document) {
10
% ### Destination is available
11
% ######################################################
12
13
<script language="JavaScript">
14
<!--
15
function DeleteSource ()
16
{
17
oSelect = document.forms['sourceform'].elements[0];
18
for (j = 0; j < oSelect.options.length; j++) {
19
if (!oSelect.options[j].selected) {
20
oSelect.options[j] = null;
21
}
22
}
23
return true;
24
}
25
26
function SelectAllSources ()
27
{
28
oSelect = document.forms['sourceform'].elements[0];
29
// oSelect.setExpression('multiple', true);
30
// document.recalc(true);
31
for (j = 0; j < oSelect.options.length; j++) {
32
oSelect.options[j].selected = true;
33
}
34
return true;
35
}
36
37
function CheckSource ()
38
{
39
oSelect = document.forms['sourceform'].elements[0];
40
if ( oSelect.options.length ) {
41
return true;
42
} else {
43
alert ('Не выбран ни один документ');
44
return false;
45
}
46
}
47
48
//-->
49
</script>
50
51
%
52
% my @properties = $document->structure();
53
% my ($prop) = grep { $_->{'attr'} eq 'status' } @properties;
54
% my ($status) = grep { $_->[0] == $document->status } @{ $m->comp( '/contenido/components/inputs/status.msn', prop => $prop, object=>$document, name => $prop->{attr}, mode => 'get') };
55
% $status = $status->[1];
56
<table width="100%" border="0" align="center"><tr><td>
57
<fieldset>
58
<legend>Цель</legend>
59
<table class="tform" width="100%">
60
<tr valign="top"><td><b>Название:</b>
61
</td><td><a href="/contenido/document.html?id=<% $document->id %>&class=<% $document->class %>" target="_top"><% $document->name %></a>
62
</td></tr>
63
<tr valign="top"><td><b>Класс:</b>
64
</td><td><% $document->class %>
65
</td></tr>
66
<tr valign="top"><td><b>Статус:</b>
67
</td><td><% $status %>
68
</td></tr>
69
</table>
70
</fieldset>
71
72
<fieldset>
73
<legend>Список связей</legend>
74
<table class="tform" width="100%">
75
<tr><td>
76
<form action="link_add.html" name="sourceform" target="_top" method="post" onsubmit="return CheckSource();">
77
<select multiple name="sources" size="20" style="width:100%">
78
</select>
79
<p>К данной цели будут привязаны только выделенные документы.</p>
80
<input type="hidden" name="class" value="<% $class %>">
81
<input type="hidden" name="dest_id" value="<% $dest_id %>">
82
<input type="hidden" name="dest_class" value="<% $dest_class %>">
83
<input type="button" value="Выделить все" onclick="SelectAllSources();">
84
<input type="button" value="Удалить лишнее" onclick="DeleteSource();">
85
<input type="submit" value="Связать выбранное">
86
</form>
87
</td></tr>
88
</table>
89
</fieldset>
90
91
</td></tr></table>
92
93
94
% }else{
95
% ### Destination is not available
96
% ######################################################
97
%
98
99
<script language="JavaScript">
100
<!--
101
function AddDest (Value, Name)
102
{
103
// alert (Name);
104
105
var oSelect = parent.frames.sourcefrm.document.forms['destform'].elements[0];
106
var Found = 0;
107
for(j=0; j < oSelect.options.length; j++) {
108
if (oSelect.options[j].value == Value) {
109
Found = 1;
110
}
111
}
112
if (!Found) {
113
var oOption = document.createElement("OPTION");
114
oOption.text=Name;
115
oOption.value=Value;
116
oOption.selected=true;
117
oSelect.options.add(oOption);
118
}
119
return false;
120
}
121
//-->
122
</script>
123
124
<& /contenido/components/link_browse.msn,
125
class => $class,
126
dest_class => $dest_class,
127
p => $p,
128
use_section => $use_section,
129
alpha => $alpha,
130
alpha_search => $alpha_search,
131
search => $search,
132
search_by => $search_by,
133
restrict_class => $restrict_class,
134
&>
135
136
% }
137
138
</body>
139
</html>
140
<%args>
141
142
$class => ''
143
$source_class => ''
144
$source_id => ''
145
$dest_class => ''
146
$dest_id => ''
147
$save => 0
148
$status => 0
149
150
$p => 1
151
$restrict_class => undef
152
$use_section => undef
153
$alpha => undef
154
$alpha_search => undef
155
$search_by => undef
156
$search => undef
157
158
</%args>
159
<%init>
160
161
my $document;
162
163
if ($dest_id) {
164
$document = $keeper->get_document_by_id ($dest_id,
165
class => $dest_class,
166
);
167
} else {
168
$dest_class = $class->available_destinations;
169
}
170
171
</%init>
utf8/plugins/users/comps/contenido/users/links/link_add.html
1
%#<pre><% Dumper(\%ARGS) %></pre>
2
<%args>
3
4
$class => undef
5
$source_id => undef
6
$source_class => undef
7
$dest_id => undef
8
$dest_class => undef
9
$sources => undef
10
$destinations => undef
11
12
</%args>
13
<%init>
14
15
abort404 unless $class;
16
17
my @documents;
18
my $ret_params;
19
if ( $source_id && $source_class && ($destinations || (ref $destinations eq 'ARRAY' && @$destinations)) ) {
20
@documents = $keeper->get_documents (
21
in_id => $destinations,
22
);
23
foreach my $doc (@documents) {
24
my $link = $class->new ($keeper);
25
$link->source_id ($source_id);
26
$link->source_class ($source_class);
27
$link->dest_id ($doc->id);
28
$link->dest_class ($doc->class);
29
$link->store;
30
}
31
$ret_params = "id=$source_id&class=$source_class";
32
} elsif ( $dest_id && $dest_class && ($sources || (ref $sources eq 'ARRAY' && @$sources)) ) {
33
@documents = $keeper->get_documents (
34
in_id => $sources,
35
);
36
foreach my $doc (@documents) {
37
my $link = $class->new ($keeper);
38
$link->dest_id ($dest_id);
39
$link->dest_class ($dest_class);
40
$link->source_id ($doc->id);
41
$link->source_class ($doc->class);
42
$link->store;
43
}
44
$ret_params = "id=$dest_id&class=$dest_class";
45
}
46
if ($ret_params) {
47
$m->redirect("Location", "/contenido/users/document.html?".$ret_params);
48
}else{
49
&abort404;
50
}
51
52
</%init>
utf8/plugins/users/comps/contenido/users/links/source.html
1
<& "/contenido/components/title.msn", title=>'Создание связей' &>
2
<style>
3
<!--
4
body {margin:5px;}
5
-->
6
</style>
7
8
% if (ref $document) {
9
% ### Source is available
10
% ######################################################
11
12
<script language="JavaScript">
13
<!--
14
function DeleteDest ()
15
{
16
oSelect = document.forms['destform'].elements[0];
17
for (j = 0; j < oSelect.options.length; j++) {
18
if (!oSelect.options[j].selected) {
19
oSelect.options[j] = null;
20
}
21
}
22
return true;
23
}
24
25
function SelectAllDest ()
26
{
27
oSelect = document.forms['destform'].elements[0];
28
// oSelect.setExpression('multiple', true);
29
// document.recalc(true);
30
for (j = 0; j < oSelect.options.length; j++) {
31
oSelect.options[j].selected = true;
32
}
33
return true;
34
}
35
36
function CheckDest ()
37
{
38
oSelect = document.forms['destform'].elements[0];
39
if ( oSelect.options.length ) {
40
return true;
41
} else {
42
alert ('Не выбран ни один документ');
43
return false;
44
}
45
}
46
47
//-->
48
</script>
49
50
%
51
% my @properties = $document->structure();
52
% my ($prop) = grep { $_->{'attr'} eq 'status' } @properties;
53
% my ($status) = grep { $_->[0] == $document->status } @{ $m->comp( '/contenido/components/inputs/status.msn', prop => $prop, object=>$document, name => $prop->{attr}, mode => 'get') };
54
% $status = $status->[1];
55
<table width="100%" border="0" align="center"><tr><td>
56
<fieldset>
57
<legend>Источник</legend>
58
<table class="tform" width="100%">
59
<tr valign="top"><td><b>Название:</b>
60
</td><td><a href="/contenido/document.html?id=<% $document->id %>&class=<% $document->class %>" target="_top"><% $document->name %></a>
61
</td></tr>
62
<tr valign="top"><td><b>Класс:</b>
63
</td><td><% $document->class %>
64
</td></tr>
65
<tr valign="top"><td><b>Статус:</b>
66
</td><td><% $status %>
67
</td></tr>
68
</table>
69
</fieldset>
70
71
<fieldset>
72
<legend>Список связей</legend>
73
<table class="tform" width="100%">
74
<tr><td>
75
<form action="link_add.html" name="destform" target="_top" method="post" onsubmit="return CheckDest();">
76
<select multiple name="destinations" size="20" style="width:100%">
77
</select>
78
<p>К источнику будут привязаны только выделенные документы.</p>
79
<input type="hidden" name="class" value="<% $class %>">
80
<input type="hidden" name="source_id" value="<% $source_id %>">
81
<input type="hidden" name="source_class" value="<% $source_class %>">
82
<input type="button" value="Выделить все" onclick="SelectAllDest();">
83
<input type="button" value="Удалить лишнее" onclick="DeleteDest();">
84
<input type="submit" value="Связать выбранное">
85
</form>
86
</td></tr>
87
</table>
88
</fieldset>
89
90
</td></tr></table>
91
92
%#<pre><% Dumper(\@properties) %></pre>
93
94
% }else{
95
% ### Source is not available
96
% ######################################################
97
%
98
99
<script language="JavaScript">
100
<!--
101
function AddSource (Value, Name)
102
{
103
// alert (Name);
104
105
var oSelect = parent.frames.destfrm.document.forms['sourceform'].elements[0];
106
var Found = 0;
107
for(j=0; j < oSelect.options.length; j++) {
108
if (oSelect.options[j].value == Value) {
109
Found = 1;
110
}
111
}
112
if (!Found) {
113
var oOption = document.createElement("OPTION");
114
oOption.text=Name;
115
oOption.value=Value;
116
oOption.selected=true;
117
oSelect.options.add(oOption);
118
}
119
return false;
120
}
121
//-->
122
</script>
123
124
<& /contenido/components/link_browse.msn,
125
class => $class,
126
source_class => $source_class,
127
p => $p,
128
use_section => $use_section,
129
alpha => $alpha,
130
alpha_search => $alpha_search,
131
search => $search,
132
search_by => $search_by,
133
restrict_class => $restrict_class,
134
&>
135
136
% }
137
138
</body>
139
</html>
140
<%args>
141
$class => ''
142
$source_class => ''
143
$source_id => ''
144
$dest_class => ''
145
$dest_id => ''
146
$save => 0
147
$status => 0
148
149
$p => 1
150
$restrict_class => undef
151
$use_section => undef
152
$alpha => undef
153
$alpha_search => undef
154
$search_by => undef
155
$search => undef
156
157
</%args>
158
<%init>
159
160
my $document;
161
162
if ($source_id) {
163
$document = $keeper->get_document_by_id ($source_id,
164
class => $source_class,
165
);
166
} else {
167
$source_class = $class->available_sources;
168
}
169
170
</%init>
utf8/plugins/users/comps/contenido/users/links/title.html
1
<& "/contenido/components/title.msn", title=>'Создание связей' &>
2
<table width="100%" cellspacing="5" cellpadding="0" border="0">
3
<tr><td style="font-size:110%;">
4
<div><b>Создание связей. Тип связи —
5
<% $class->class_name %> (<span style="color:olive;"><% $class %></span>)</b></div>
6
</td></tr>
7
</table>
8
</body>
9
</html>
10
<%args>
11
$class => ''
12
$source_class => ''
13
$source_id => ''
14
$dest_class => ''
15
$dest_id => ''
16
$save => 0
17
$status => 0
18
</%args>
utf8/plugins/users/comps/contenido/users/store_document_sections.html
1
<%ARGS>
2
$id => undef
3
$class => undef
4
</%ARGS>
5
6
<%INIT>
7
if ($id)
8
{
9
my $document = $keeper->get_document_by_id($id, class=>$class);
10
11
my %sections = ();
12
foreach my $name (keys %ARGS)
13
{
14
if ($name =~ /^section_(\d+)$/)
15
{
16
$sections{$1} = 1;
17
}
18
}
19
my @sections_in_order = ();
20
if ($ARGS{main_section} > 0)
21
{
22
delete $sections{ $ARGS{main_section} };
23
push (@sections_in_order, $ARGS{main_section});
24
}
25
push (@sections_in_order, keys(%sections));
26
27
$document->sections(@sections_in_order);
28
$document->store();
29
30
$r->header_out("Location", "document.html?id=$id&class=$class");
31
$r->status(302);
32
$r->send_http_header();
33
$m->abort();
34
}
35
36
</%init>
utf8/plugins/users/config.proto
1
#############################################################################
2
#
3
# Параметры данного шаблона необходимо ВРУЧНУЮ добавить в config.mk проекта
4
# и привести в соответствие с требованиями проекта
5
#
6
#############################################################################
7
8
PLUGINS += users
9
10
PROFILE_DOCUMENT_CLASS = MyProject::Profile
11
REWRITE += PROFILE_DOCUMENT_CLASS
12
13
PROFILE_AUTH_METHOD = LDAP
14
LDAP_LOGIN = <Full LDAP DSN>
15
LDAP_PASSWORD = <LDAP Password>
16
REWRITE += PROFILE_AUTH_METHOD LDAP_LOGIN LDAP_PASSWORD
17
18
PROFILE_AUTH_METHOD = RPC
19
REWRITE += PROFILE_AUTH_METHOD
20
21
22
########################################################################
23
#
24
# PROFILE_DOCUMENT_CLASS
25
# Добавляется при перекрытии стандартного класса users::Profile
26
# классом из проекта. В этом случае все методы будут дергать
27
# проектный класс
28
# PROFILE_AUTH_METHOD
29
# Метод авторизации. Если не указать, будет использоваться
30
# локальная авторизация. Варианты: LDAP, RPC
31
# LDAP_LOGIN и LDAP_PASSWORD
32
# Параметры учетной записи в LDAP, с помощью которой осуществляется
33
# поиск и авторизация всех пользователей.
34
#
35
########################################################################
utf8/plugins/users/lib/users/Apache.pm
1
package users::Apache;
2
3
use strict;
4
use warnings 'all';
5
6
use users::State;
7
use Contenido::Globals;
8
9
10
sub child_init {
11
# встраиваем keeper плагина в keeper проекта
12
$keeper->{users} = users::Keeper->new($state->users);
13
}
14
15
sub request_init {
16
}
17
18
sub child_exit {
19
}
20
21
1;
utf8/plugins/users/lib/users/Init.pm
1
package users::Init;
2
3
use strict;
4
use warnings 'all';
5
6
use Contenido::Globals;
7
use users::Apache;
8
use users::Keeper;
9
10
11
# загрузка всех необходимых плагину классов
12
# users::SQL::SomeTable
13
# users::SomeClass
14
Contenido::Init::load_classes(qw(
15
users::SQL::UserProfile
16
17
users::UserProfile
18
users::Section
19
));
20
21
sub init {
22
push @{ $state->{'available_documents'} }, 'users::UserProfile' if $state->{users}->profile_document_class eq 'users::UserProfile';
23
push @{ $state->{'available_sections'} }, 'users::Section';
24
0;
25
}
26
27
1;
utf8/plugins/users/lib/users/Keeper.pm
1
package users::Keeper;
2
3
use strict;
4
use warnings 'all';
5
use base qw(Contenido::Keeper);
6
7
use Digest::MD5;
8
use Contenido::Globals;
9
use Data::Dumper;
10
11
# ----------------------------------------------------------------------------
12
# Функции:
13
# ----------------------------------------------------------------------------
14
# check_login: Наличие пользователя в системе
15
#
16
# login => login пользователя
17
# email => email пользователя
18
# ----------------------------------------------------------------------------
19
sub check_login {
20
21
my $self = shift;
22
my %opts = @_;
23
24
return if !$opts{login} && !$opts{email};
25
my $class = $self->state->profile_document_class;
26
my $res = $keeper->get_documents (
27
class => $class,
28
$opts{login} ? (login => $opts{login}) : (),
29
$opts{email} ? (email => lc($opts{email})) : (),
30
count => 1,
31
);
32
return int($res);
33
}
34
35
36
37
# ----------------------------------------------------------------------------
38
# login: Авторизация пользователя
39
#
40
# login => login пользователя
41
# email => e-mail пользователя
42
# passwd => пароль пользователя
43
# ----------------------------------------------------------------------------
44
sub login {
45
46
my $self = shift;
47
my %opts = @_;
48
49
return if !($opts{login} || $opts{email}) && !$opts{passwd};
50
my $passmd5 = Digest::MD5::md5_hex($opts{passwd});
51
my $class = $self->state->profile_document_class;
52
my $res = $keeper->get_documents (
53
class => $class,
54
$opts{login} ? (login => $opts{login}) : (),
55
$opts{email} ? (email => lc($opts{email})) : (),
56
status => [1,2,3,4,5],
57
return_mode => 'array_ref',
58
);
59
$res = ref $res eq 'ARRAY' ? $res->[0] : undef;
60
return unless ref $res;
61
# warn "Password = ".$opts{passwd}."; Pass MD5 = $passmd5; user MD5 = ".$res->passwd."\n" if $DEBUG;
62
if ($res->passwd eq $passmd5 ) {
63
my $lastlogin = Contenido::DateTime->new ( postgres => $res->lastlogin );
64
my $now = Contenido::DateTime->new;
65
my $then = $now->clone;
66
$then->subtract( hours => 2 );
67
if ( DateTime->compare($then, $lastlogin) == 1 ) {
68
$res->lastlogin( $now->ymd('-').' '.$now->hms );
69
$res->passwd(undef);
70
$res->store;
71
$res->lastlogin( $lastlogin->ymd('-').' '.$lastlogin->hms );
72
}
73
return $res;
74
}else{
75
return;
76
}
77
78
}
79
80
81
82
# ----------------------------------------------------------------------------
83
# confirm: Подтверждение аутентификации
84
#
85
# login => login пользователя
86
# email => e-mail пользователя
87
# md5 => MD5 пароля пользователя
88
# ----------------------------------------------------------------------------
89
sub confirm {
90
91
my $self = shift;
92
my %opts = @_;
93
94
return if !($opts{login} || $opts{email}) && !$opts{md5};
95
my $class = $self->state->profile_document_class;
96
my $res = $keeper->get_documents (
97
class => $class,
98
$opts{login} ? (login => $opts{login}) : (),
99
$opts{email} ? (email => lc($opts{email})) : (),
100
return_mode => 'array_ref',
101
);
102
$res = ref $res eq 'ARRAY' && scalar @$res ? $res->[0] : undef;
103
return unless ref $res;
104
warn "MD5 = ".$opts{md5}."; user MD5 = ".$res->passwd."\n" if $DEBUG;
105
if ($res->passwd eq $opts{md5} ) {
106
my $now = localtime;
107
$res->lastlogin( $now );
108
$res->passwd(undef);
109
$res->status(1);
110
$res->store;
111
return $res;
112
}else{
113
return;
114
}
115
116
}
117
118
119
120
# ----------------------------------------------------------------------------
121
# get_profile: Вытащить профиль пользователя
122
#
123
# id => по ID
124
# login => по login
125
# email => по e-mail
126
# nickname=> по никнейму
127
# status => фильтр по статусу
128
# ----------------------------------------------------------------------------
129
sub get_profile {
130
131
my $self = shift;
132
my %opts = @_;
133
134
return if !$opts{login} && !$opts{id} && !$opts{email} && !$opts{nickname};
135
my $class = $self->state->profile_document_class;
136
my $res = $keeper->get_documents (
137
class => $class,
138
%opts,
139
return_mode => 'array_ref',
140
);
141
$res = ref $res eq 'ARRAY' && scalar @$res ? $res->[0] : undef;
142
return $res;
143
}
144
145
1;
utf8/plugins/users/lib/users/Section.pm
1
package users::Section;
2
3
use Contenido::Section;
4
@ISA = ('Contenido::Section');
5
6
sub extra_properties
7
{
8
return (
9
)
10
}
11
12
sub class_name
13
{
14
return 'Секция пользовательских профилей';
15
}
16
17
sub class_description
18
{
19
return 'Секция пользовательских профилей';
20
}
21
22
23
1;
utf8/plugins/users/lib/users/SQL/UserProfile.pm
1
package users::SQL::UserProfile;
2
3
use base 'SQL::DocumentTable';
4
5
sub db_table
6
{
7
return 'profiles';
8
}
9
10
my $available_filters = [qw(
11
12
_class_filter
13
_status_filter
14
_in_id_filter
15
_id_filter
16
_name_filter
17
_class_excludes_filter
18
_sfilter_filter
19
_excludes_filter
20
_datetime_filter
21
_date_equal_filter
22
_date_filter
23
_previous_days_filter
24
_s_filter
25
26
_login_filter
27
_email_filter
28
_nickname_filter
29
)];
30
31
sub available_filters {
32
return $available_filters;
33
}
34
35
my @required_properties = (
36
{ # Идентификатор документа, сквозной по всем типам...
37
'attr' => 'id',
38
'type' => 'integer',
39
'rusname' => 'Идентификатор документа',
40
'hidden' => 1,
41
'readonly' => 1,
42
'auto' => 1,
43
'db_field' => 'id',
44
'db_type' => 'integer',
45
'db_opts' => "not null default nextval('public.documents_id_seq'::text)",
46
},
47
{ # Класс документа...
48
'attr' => 'class',
49
'type' => 'string',
50
'rusname' => 'Класс документа',
51
'hidden' => 1,
52
'readonly' => 1,
53
'db_field' => 'class',
54
'db_type' => 'varchar(48)',
55
'db_opts' => 'not null',
56
},
57
{ # Ф.И.О....
58
'attr' => 'name',
59
'type' => 'string',
60
'rusname' => 'Ф.И.О.',
61
'column' => 2,
62
'db_field' => 'name',
63
'db_type' => 'varchar(255)',
64
},
65
{ # Время создания документа, служебное поле...
66
'attr' => 'ctime',
67
'type' => 'datetime',
68
'rusname' => 'Время создания',
69
'readonly' => 1,
70
'auto' => 1,
71
'hidden' => 1,
72
'db_field' => 'ctime',
73
'db_type' => 'timestamp',
74
'db_opts' => 'not null default now()',
75
'default' => 'CURRENT_TIMESTAMP',
76
},
77
{ # Время модификации документа, служебное поле...
78
'attr' => 'mtime',
79
'type' => 'datetime',
80
'rusname' => 'Время модификации',
81
'hidden' => 1,
82
'auto' => 1,
83
'db_field' => 'mtime',
84
'db_type' => 'timestamp',
85
'db_opts' => 'not null default now()',
86
'default' => 'CURRENT_TIMESTAMP',
87
},
88
{ # Дата рождения
89
'attr' => 'dtime',
90
'type' => 'date',
91
'rusname' => 'Дата рождения',
92
'db_field' => 'dtime',
93
'db_type' => 'timestamp',
94
'default' => 'CURRENT_TIMESTAMP',
95
},
96
{ # Дата и время логина...
97
'attr' => 'lastlogin',
98
'type' => 'datetime',
99
'rusname' => 'Дата и время последнего логина<sup style="color:#888;"> 1)</sup>',
100
'column' => 1,
101
'db_field' => 'lastlogin',
102
'db_type' => 'timestamp',
103
'db_opts' => 'not null default now()',
104
'default' => 'CURRENT_TIMESTAMP',
105
},
106
{ # Массив секций, обрабатывается специальным образом...
107
'attr' => 'sections',
108
'type' => 'sections_list',
109
'rusname' => 'Секции',
110
'hidden' => 1,
111
'db_field' => 'sections',
112
'db_type' => 'integer[]',
113
},
114
{ # Одно поле статуса является встроенным...
115
'attr' => 'status',
116
'type' => 'status',
117
'rusname' => 'Статус',
118
'db_field' => 'status',
119
'db_type' => 'integer',
120
},
121
{ # Дополнительное поле статуса
122
'attr' => 'type',
123
'type' => 'integer',
124
'rusname' => 'Тип пользователя (project-oriented)',
125
'db_field' => 'type',
126
'db_type' => 'integer',
127
},
128
{ # Никнейм...
129
'attr' => 'nickname',
130
'type' => 'string',
131
'rusname' => 'Ник',
132
'column' => 3,
133
'db_field' => 'nickname',
134
'db_type' => 'text',
135
},
136
{ # Login...
137
'attr' => 'login',
138
'type' => 'string',
139
'rusname' => 'Логин',
140
'column' => 4,
141
'db_field' => 'login',
142
'db_type' => 'text',
143
},
144
{ # Login method...
145
'attr' => 'login_method',
146
'type' => 'string',
147
'rusname' => 'Метод входа',
148
'db_field' => 'login_method',
149
'db_type' => 'text',
150
},
151
{ # E-mail...
152
'attr' => 'email',
153
'type' => 'string',
154
'rusname' => 'E-mail (primary)',
155
'db_field' => 'email',
156
'db_type' => 'text',
157
},
158
159
);
160
161
# ----------------------------------------------------------------------------
162
# Свойства храним в массивах, потому что порядок важен!
163
# Это общие свойства - одинаковые для всех документов.
164
#
165
# attr - обязательный параметр, название атрибута;
166
# type - тип аттрибута, требуется для отображдения;
167
# rusname - русское название, опять же требуется для отображения;
168
# hidden - равен 1, когда
169
# readonly - инициализации при записи только без изменения в дальнейшем
170
# db_field - поле в таблице
171
# default - значение по умолчанию (поле всегда имеет это значение)
172
# ----------------------------------------------------------------------------
173
sub required_properties
174
{
175
return @required_properties;
176
}
177
178
########### FILTERS DESCRIPTION ####################################################################################
179
sub _login_filter {
180
my ($self,%opts)=@_;
181
return undef unless ( exists($opts{login}) );
182
if (exists $opts{ilike} && $opts{ilike} == 1) {
183
return &SQL::Common::_generic_name_filter('d.login', $opts{login}, 0, \%opts);
184
}else{
185
return &SQL::Common::_generic_text_filter('d.login', $opts{login});
186
}
187
}
188
189
sub _email_filter {
190
my ($self,%opts)=@_;
191
return undef unless ( exists($opts{email}) );
192
if (exists $opts{ilike} && $opts{ilike} == 1) {
193
return &SQL::Common::_generic_name_filter('d.email', $opts{email}, 0, \%opts);
194
}else{
195
return &SQL::Common::_generic_text_filter('d.email', $opts{email});
196
}
197
}
198
199
sub _nickname_filter {
200
my ($self,%opts)=@_;
201
return undef unless ( exists($opts{nickname}) );
202
if (exists $opts{ilike} && $opts{ilike} == 1) {
203
return &SQL::Common::_generic_name_filter('d.nickname', $opts{nickname}, 0, \%opts);
204
}else{
205
return &SQL::Common::_generic_text_filter('d.nickname', $opts{nickname});
206
}
207
}
208
209
1;
utf8/plugins/users/lib/users/State.pm.proto
1
package users::State;
2
3
use strict;
4
use warnings 'all';
5
use vars qw($AUTOLOAD);
6
7
8
sub new {
9
my ($proto) = @_;
10
my $class = ref($proto) || $proto;
11
my $self = {};
12
bless $self, $class;
13
14
# зашитая конфигурация плагина
15
$self->{db_type} = 'none';
16
$self->{db_keepalive} = 0;
17
$self->{db_host} = '';
18
$self->{db_name} = '';
19
$self->{db_user} = '';
20
$self->{db_password} = '';
21
$self->{db_port} = '';
22
$self->{profile_document_class} = '@PROFILE_DOCUMENT_CLASS@' || 'users::UserProfile';
23
24
$self->{data_directory} = '';
25
$self->{images_directory} = '';
26
$self->{binary_directory} = '';
27
$self->{preview} = '';
28
$self->{debug} = '';
29
$self->{store_method} = '';
30
$self->{cascade} = '';
31
$self->{memcached_enable} = '';
32
33
$self->_init_();
34
$self;
35
}
36
37
sub info {
38
my $self = shift;
39
return unless ref $self;
40
41
for (sort keys %{$self->{attributes}}) {
42
my $la = length $_;
43
warn "\t$_".("\t" x (2-int($la/8))).": $self->{$_}\n";
44
}
45
}
46
47
sub _init_ {
48
my $self = shift;
49
50
# зашитая конфигурация плагина
51
$self->{attributes}->{$_} = 'SCALAR' for qw(
52
db_type
53
profile_document_class
54
db_keepalive
55
db_host
56
db_port
57
db_name
58
db_user
59
db_password
60
data_directory images_directory binary_directory preview debug store_method cascade memcached_enable
61
);
62
}
63
64
sub AUTOLOAD {
65
my $self = shift;
66
my $attribute = $AUTOLOAD;
67
68
$attribute =~ s/.*:://;
69
return unless $attribute =~ /[^A-Z]/; # Отключаем методы типа DESTROY
70
71
if (!exists $self->{attributes}->{$attribute}) {
72
warn "Contenido Error (users::State): Вызов метода, для которого не существует обрабатываемого свойства: ->$attribute()\n";
73
return;
74
}
75
76
$self->{$attribute} = shift @_ if $#_>=0;
77
$self->{$attribute};
78
}
79
80
1;
utf8/plugins/users/lib/users/UserProfile.pm
1
package users::UserProfile;
2
3
use base "Contenido::Document";
4
use Digest::MD5;
5
use Contenido::Globals;
6
7
sub extra_properties
8
{
9
return (
10
{ 'attr' => 'status', 'type' => 'status', 'rusname' => 'Статус пользователя',
11
'cases' => [
12
[0, 'Блокированный'],
13
[1, 'Активный'],
14
[2, 'Платный'],
15
[3, 'Свой/сотрудник'],
16
[4, 'VIP'],
17
[5, 'Временная активация'],
18
],
19
},
20
{ 'attr' => 'visibility', 'type' => 'status', 'rusname' => 'Область видимости',
21
'cases' => [
22
[0, 'Данные моего аккаунта видны только мне'],
23
[1, 'Данные моего аккаунта видны всем'],
24
[2, 'Данные моего аккаунта видны друзьям'],
25
[3, 'Данные моего аккаунта видны друзьям и членам клубов'],
26
],
27
},
28
{ 'attr' => 'country', 'type' => 'string', 'rusname' => 'Страна' },
29
{ 'attr' => 'passwd', 'type' => 'password', 'rusname' => 'Пароль', 'rem' => '(<font color="red">Не отображается. Указывать при создании и для изменения</font>)' },
30
{ 'attr' => 'confirm', 'type' => 'string', 'rusname' => 'Код подтверждения', hidden => 1 },
31
{ 'attr' => 'secmail', 'type' => 'string', 'rusname' => 'E-mail (secondary)' },
32
{ 'attr' => 'q1', 'type' => 'string', 'rusname' => 'Контрольный вопрос 1' },
33
{ 'attr' => 'a1', 'type' => 'string', 'rusname' => 'Контрольный ответ 1' },
34
{ 'attr' => 'q2', 'type' => 'string', 'rusname' => 'Контрольный вопрос 2' },
35
{ 'attr' => 'a2', 'type' => 'string', 'rusname' => 'Контрольный ответ 2' },
36
{ 'attr' => 'account', 'type' => 'string', 'rusname' => 'Сумма на счете' },
37
{ 'attr' => 'interests', 'type' => 'text', 'rusname' => 'Жизненные интересы', rows => 10 },
38
{ 'attr' => 'origin', 'type' => 'text', 'rusname' => 'Ориджин', rows => 4 },
39
{ 'attr' => 'avatar', 'type' => 'image', 'rusname' => 'Аватар', preview => ['32x32','100x100','120x120','160x160'] },
40
)
41
}
42
43
sub class_name
44
{
45
return 'Профиль пользователя';
46
}
47
48
sub class_description
49
{
50
return 'Профиль пользователя';
51
}
52
53
sub search_fields
54
{
55
return ('email', 'name', 'login');
56
}
57
58
sub class_table
59
{
60
return 'users::SQL::UserProfile';
61
}
62
63
sub contenido_status_style
64
{
65
my $self = shift;
66
if ( $self->status == 2 ) {
67
return 'color:green;';
68
} elsif ( $self->status == 3 ) {
69
return 'color:olive;';
70
} elsif ( $self->status == 4 ) {
71
return 'color:green;';
72
} elsif ( $self->status == 5 ) {
73
return 'color:red;';
74
}
75
}
76
77
sub pre_store
78
{
79
my $self = shift;
80
81
my $up = $self->{keeper}->get_document_by_id ( $self->id,
82
class => $self->class,
83
);
84
if ( (ref $up && $self->passwd && $self->passwd ne $up->passwd) || (!ref $up && $self->passwd) ) {
85
warn "Pass = ".$self->passwd."\n" if $DEBUG;
86
my $pass = Digest::MD5::md5_hex($self->passwd);
87
warn "Pass_hex = $pass\n" if $DEBUG;
88
$self->passwd($pass);
89
} elsif ( ref $up && (!$self->passwd || $self->passwd eq $up->passwd ) ) {
90
$self->passwd($up->passwd);
91
}
92
$self->email(lc($self->email));
93
$self->login(lc($self->login));
94
95
my $default_section = $project->s_alias->{users} if ref $project->s_alias eq 'HASH' && exists $project->s_alias->{users};
96
if ( $default_section ) {
97
my $sections = $self->{sections};
98
if ( ref $sections eq 'ARRAY' && scalar @$sections ) {
99
my @new_sects = grep { $_ != $default_section } @$sections;
100
push @new_sects, $default_section;
101
$self->sections(@new_sects);
102
} elsif ( $sections && !ref $sections && $sections != $default_section ) {
103
my @new_sects = ($default_section, $sections);
104
$self->sections(@new_sects);
105
} else {
106
$self->sections($default_section);
107
}
108
}
109
110
return 1;
111
}
112
1;
utf8/plugins/users/sql/TOAST/profiles.sql
1
create table profiles
2
(
3
id integer not null primary key default nextval('public.documents_id_seq'::text),
4
class text not null,
5
ctime timestamp not null default now(),
6
mtime timestamp not null default now(),
7
dtime timestamp,
8
lastlogin timestamp not null default now(),
9
status smallint not null default 0,
10
type integer,
11
sections integer[],
12
name text,
13
nickname text,
14
login text,
15
email text,
16
login_method text default 'local',
17
data text
18
);
19
create index profiles_sections on profiles using gist ( "sections" "gist__int_ops" );
20
create index profiles_lastlogin on profiles (lastlogin);
21
create unique index profiles_login on profiles (login) where login is not null and login != '';
22
create unique index profiles_email on profiles (email) where email is not null and email != '';
Небольшая справка по веткам
cnddist – контейнер, в котором хранятся все дистрибутивы всех библиотек и программных пакетов, которые использовались при построении различных версий Contenido. Если какой-то библиотеки в данном хранилище нет, инсталлятор сделает попытку "подтянуть" ее с веба (например, с CPAN). Если библиотека слишком старая, есть очень большая вероятность, что ее там уже нет. Поэтому мы храним весь хлам от всех сборок. Если какой-то дистрибутив вдруг отсутствует в cnddist - напишите нам, мы положим его туда.
koi8 – отмирающая ветка, чей код, выдача и все внутренние библиотеки заточены на кодировку KOI8-R. Вносятся только те дополнения, которые касаются внешнего вида и функционала админки, баги ядра, обязательные обновления портов и мелочи, которые легко скопипастить. В дальнейшем планируется полная остановка поддержки по данной ветке.
utf8 – актуальная ветка, заточенная под UTF-8.
Внутри каждой ветки: core – исходники ядра; install – скрипт установки инсталляции; plugins – плагины; samples – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.