Revision 196
- Date:
- 2012/03/15 18:28:32
- Files:
-
- /utf8/plugins/users
- /utf8/plugins/users/comps
- /utf8/plugins/users/comps/contenido
- /utf8/plugins/users/comps/contenido/users
- /utf8/plugins/users/comps/contenido/users/autohandler (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/components
- /utf8/plugins/users/comps/contenido/users/components/__section_tree__.msn (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/components/find_user.msn (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/components/subsection.msn (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/components/tasks.msn (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/dhandler (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/document.html (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/document_filter_list.html (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/index.html (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/link_frame.html (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/links
- /utf8/plugins/users/comps/contenido/users/links/destination.html (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/links/link_add.html (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/links/source.html (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/links/title.html (Diff) (Checkout)
- /utf8/plugins/users/comps/contenido/users/store_document_sections.html (Diff) (Checkout)
- /utf8/plugins/users/config.proto (Diff) (Checkout)
- /utf8/plugins/users/lib
- /utf8/plugins/users/lib/users
- /utf8/plugins/users/lib/users/Apache.pm (Diff) (Checkout)
- /utf8/plugins/users/lib/users/Init.pm (Diff) (Checkout)
- /utf8/plugins/users/lib/users/Keeper.pm (Diff) (Checkout)
- /utf8/plugins/users/lib/users/SQL
- /utf8/plugins/users/lib/users/SQL/UserProfile.pm (Diff) (Checkout)
- /utf8/plugins/users/lib/users/Section.pm (Diff) (Checkout)
- /utf8/plugins/users/lib/users/State.pm.proto (Diff) (Checkout)
- /utf8/plugins/users/lib/users/UserProfile.pm (Diff) (Checkout)
- /utf8/plugins/users/sql
- /utf8/plugins/users/sql/TOAST
- /utf8/plugins/users/sql/TOAST/profiles.sql (Diff) (Checkout)
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 != '';