# vim: set et ts=4 sw=4: package AdminPanel::Module::Users; #============================================================= -*-perl-*- =head1 NAME AdminPanel::Module::Users - This module aims to manage service with GUI =head1 SYNOPSIS my $userManager = AdminPanel::Module::Users->new(); $userManager->start(); =head1 DESCRIPTION This module is a tool to manage users on the system. From the original code adduserdrake and userdrake. =head1 SUPPORT You can find documentation for this module with the perldoc command: perldoc AdminPanel::Module::Users =head1 SEE ALSO AdminPanel::Module =head1 AUTHOR Angelo Naselli =head1 COPYRIGHT and LICENSE Copyright (C) 2013, Angelo Naselli. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA =head1 VERSION Version 1.0.0 =cut our $VERSION = '1.0.0'; ############################################### ## ## graphic related routines for managing user ## ############################################### use Moose; use POSIX qw(ceil); use Config::Auto; use File::ShareDir ':ALL'; use utf8; use Sys::Syslog; use Glib; use English; use yui; use AdminPanel::Shared; use AdminPanel::Shared::GUI; use AdminPanel::Shared::Locales; use AdminPanel::Shared::Users; use MDK::Common::DataStructure qw(member); extends qw( AdminPanel::Module ); has '+icon' => ( default => "/usr/share/icons/userdrake.png", ); # main dialog has 'dialog' => ( is => 'rw', init_arg => undef, ); has 'widgets' => ( traits => ['Hash'], default => sub { {} }, is => 'rw', isa => 'HashRef', handles => { set_widget => 'set', get_widget => 'get', widget_pairs => 'kv', }, init_arg => undef, ); has 'action_menu' => ( traits => ['Hash'], default => sub { {} }, is => 'rw', isa => 'HashRef', handles => { set_action_menu => 'set', get_action_menu => 'get', action_menu_pairs => 'kv', }, init_arg => undef, ); has 'edit_tab_widgets' => ( traits => ['Hash'], default => sub { {} }, is => 'rw', isa => 'HashRef', handles => { set_edit_tab_widget => 'set', get_edit_tab_widget => 'get', edit_tab_pairs => 'kv', }, init_arg => undef, ); has 'sh_users' => ( is => 'rw', init_arg => undef, builder => '_SharedUsersInitialize' ); sub _SharedUsersInitialize { my $self = shift(); $self->sh_users(AdminPanel::Shared::Users->new() ); } has 'sh_gui' => ( is => 'rw', init_arg => undef, builder => '_SharedUGUIInitialize' ); sub _SharedUGUIInitialize { my $self = shift(); $self->sh_gui(AdminPanel::Shared::GUI->new() ); } has 'loc' => ( is => 'rw', init_arg => undef, builder => '_localeInitialize' ); sub _localeInitialize { my $self = shift(); # TODO fix domain binding for translation $self->loc(AdminPanel::Shared::Locales->new(domain_name => 'userdrake') ); # TODO if we want to give the opportunity to test locally add dir_name => 'path' } #============================================================= =head1 METHODS =cut =head2 new - additional parameters =head3 config_file optional parameter to set the configuration file name =cut has 'config_file' => ( is => 'rw', isa => 'Str', default => '/etc/sysconfig/adminuser', ); #============================================================= =head2 start =head3 INPUT $self: this object =head3 DESCRIPTION This method extends Module::start and is invoked to start adminService =cut #============================================================= sub start { my $self = shift; $self->_manageUsersDialog(); }; # TODO move to Shared? sub _labeledFrameBox { my ($parent, $label) = @_; my $factory = yui::YUI::widgetFactory; my $frame = $factory->createFrame($parent, $label); $frame->setWeight( $yui::YD_HORIZ, 1); $frame->setWeight( $yui::YD_VERT, 2); $frame = $factory->createHVCenter( $frame ); $frame = $factory->createVBox( $frame ); return $frame; } # usefull local variable to avoid duplicating # translation point for user edit labels my %userEditLabel; # usefull local variable to avoid duplicating # translation point for group edit labels my %groupEditLabel; #============================================================= =head2 BUILD =head3 INPUT $self: this object =head3 DESCRIPTION The BUILD method is called after a Moose object is created, Into this method additional data are initialized. =cut #============================================================= sub BUILD { my $self = shift; if (! $self->name) { $self->name ($self->loc->N("adminUser")); } %userEditLabel = ( user_data => $self->loc->N("User Data"), account_info => $self->loc->N("Account Info"), password_info => $self->loc->N("Password Info"), groups => $self->loc->N("Groups"), ); %groupEditLabel = ( group_data => $self->loc->N("Group Data"), group_users => $self->loc->N("Group Users"), ); } #============================================================= =head2 ChooseGroup =head3 INPUT $self: this object =head3 OUTPUT $choice: 0 or 1 (choice) -1 cancel or exit =head3 DESCRIPTION creates a popup dialog to ask if adding user to an existing group or to the 'users' group =cut #============================================================= sub ChooseGroup { my $self = shift; my $choice = -1; ## push application title my $appTitle = yui::YUI::app()->applicationTitle(); ## set new title to get it in dialog yui::YUI::app()->setApplicationTitle($self->loc->N("Choose group")); my $factory = yui::YUI::widgetFactory; my $dlg = $factory->createPopupDialog(); my $layout = $factory->createVBox($dlg); my $frame = _labeledFrameBox($layout, $self->loc->N("A group with this name already exists. What would you like to do?")); my $rbg = $factory->createRadioButtonGroup( $frame ); $frame = $factory->createVBox( $rbg ); my $align = $factory->createLeft($frame); my $rb1 = $factory->createRadioButton( $align, $self->loc->N("Add to the existing group"), 1); $rb1->setNotify(1); $rbg->addRadioButton( $rb1 ); $align = $factory->createLeft($frame); my $rb2 = $factory->createRadioButton( $align, $self->loc->N("Add to the 'users' group"), 0); $rb2->setNotify(1); $rbg->addRadioButton( $rb2 ); my $hbox = $factory->createHBox($layout); $align = $factory->createRight($hbox); my $cancelButton = $factory->createPushButton($align, $self->loc->N("Cancel")); my $okButton = $factory->createPushButton($hbox, $self->loc->N("Ok")); while(1) { my $event = $dlg->waitForEvent(); my $eventType = $event->eventType(); #event type checking if ($eventType == $yui::YEvent::CancelEvent) { last; } elsif ($eventType == $yui::YEvent::WidgetEvent) { # widget selected my $widget = $event->widget(); if ($widget == $cancelButton) { last; } if ($widget == $okButton) { $choice = $rb1->value() ? 0 : 1 ; last; } } } destroy $dlg; #restore old application title yui::YUI::app()->setApplicationTitle($appTitle); return $choice; } #============================================================= =head2 _deleteGroupDialog =head3 INPUT $self: this object =head3 DESCRIPTION This method open a dialog to delete the selected group. =cut #============================================================= sub _deleteGroupDialog { my $self = shift; my $item = $self->get_widget('table')->selectedItem(); if (! $item) { return; } my $groupname = $item->label(); ## push application title my $appTitle = yui::YUI::app()->applicationTitle(); ## set new title to get it in dialog yui::YUI::app()->setApplicationTitle($self->loc->N("Warning")); my $factory = yui::YUI::widgetFactory; my $dlg = $factory->createPopupDialog(); my $layout = $factory->createVBox($dlg); my $align = $factory->createLeft($layout); $factory->createLabel($align, $self->loc->N("Do you really want to delete the group %s?", $groupname)); $align = $factory->createRight($layout); my $hbox = $factory->createHBox($align); my $cancelButton = $factory->createPushButton($hbox, $self->loc->N("Cancel")); my $deleteButton = $factory->createPushButton($hbox, $self->loc->N("Delete")); while(1) { my $event = $dlg->waitForEvent(); my $eventType = $event->eventType(); #event type checking if ($eventType == $yui::YEvent::CancelEvent) { last; } elsif ($eventType == $yui::YEvent::WidgetEvent) { # widget selected my $widget = $event->widget(); if ($widget == $cancelButton) { last; } elsif ($widget == $deleteButton) { my $username = $self->sh_users->isPrimaryGroup($groupname); if (defined($username)) { $self->sh_gui->msgBox({ text => $self->loc->N("%s is a primary group for user %s\n Remove the user first", $groupname, $username ) }); } else { if ($self->sh_users->deleteGroup($groupname)) { Sys::Syslog::syslog('info|local1', $self->loc->N("Removing group: %s", $groupname)); } $self->_refresh(); } last; } } } destroy $dlg; #restore old application title yui::YUI::app()->setApplicationTitle($appTitle); } #============================================================= =head2 _deleteUserDialog =head3 INPUT $self: this object =head3 DESCRIPTION This method open a dialog to delete the selected user. It also asks for additional information to be removed. =cut #============================================================= sub _deleteUserDialog { my $self = shift; my $item = $self->get_widget('table')->selectedItem(); if (! $item) { return; } my $username = $item->label(); my $homedir = $self->sh_users->getUserHome($username); return if !defined($homedir); ## push application title my $appTitle = yui::YUI::app()->applicationTitle(); ## set new title to get it in dialog yui::YUI::app()->setApplicationTitle($self->loc->N("Delete files or not?")); my $factory = yui::YUI::widgetFactory; my $dlg = $factory->createPopupDialog(); my $layout = $factory->createVBox($dlg); my $align = $factory->createLeft($layout); $factory->createLabel($align, $self->loc->N("Deleting user %s\nAlso perform the following actions\n", $username)); $align = $factory->createLeft($layout); my $checkhome = $factory->createCheckBox($align, $self->loc->N("Delete Home Directory: %s", $homedir), 0); $align = $factory->createLeft($layout); my $checkspool = $factory->createCheckBox($align, $self->loc->N("Delete Mailbox: /var/spool/mail/%s", $username), 0); $align = $factory->createRight($layout); my $hbox = $factory->createHBox($align); my $cancelButton = $factory->createPushButton($hbox, $self->loc->N("Cancel")); my $deleteButton = $factory->createPushButton($hbox, $self->loc->N("Delete")); if ($homedir !~ m!(?:/home|/var/spool)!) { $checkhome->setDisabled(); $checkspool->setDisabled(); } while(1) { my $event = $dlg->waitForEvent(); my $eventType = $event->eventType(); #event type checking if ($eventType == $yui::YEvent::CancelEvent) { last; } elsif ($eventType == $yui::YEvent::WidgetEvent) { # widget selected my $widget = $event->widget(); if ($widget == $cancelButton) { last; } elsif ($widget == $deleteButton) { Sys::Syslog::syslog('info|local1', $self->loc->N("Removing user: %s", $username)); my $option = undef; $option->{clean_home} = $checkhome->isChecked() if $checkhome->isChecked(); $option->{clean_spool} = $checkspool->isChecked() if $checkspool->isChecked(); my $err = $self->sh_users->deleteUser($username, $option); $self->sh_gui->msgBox({text => $err}) if (defined($err)); #remove added icon $self->sh_users->removeKdmIcon($username); $self->_refresh(); last; } } } destroy $dlg; #restore old application title yui::YUI::app()->setApplicationTitle($appTitle); } sub _addGroupDialog { my $self = shift; my $is_system = 0; ## push application title my $appTitle = yui::YUI::app()->applicationTitle(); ## set new title to get it in dialog yui::YUI::app()->setApplicationTitle($self->loc->N("Create New Group")); my $factory = yui::YUI::widgetFactory; my $optional = yui::YUI::optionalWidgetFactory; my $dlg = $factory->createPopupDialog(); my $layout = $factory->createVBox($dlg); ## 'group name' my $align = $factory->createRight($layout); my $hbox = $factory->createHBox($align); my $label = $factory->createLabel($hbox, $self->loc->N("Group Name:") ); my $groupName = $factory->createInputField($hbox, "", 0); $label->setWeight($yui::YD_HORIZ, 1); $groupName->setWeight($yui::YD_HORIZ, 2); $factory->createVSpacing($layout, 1); # Specify group id manually $align = $factory->createLeft($layout); $hbox = $factory->createHBox($align); my $gidManually = $factory->createCheckBox($hbox, $self->loc->N("Specify group ID manually"), 0); $factory->createHSpacing($hbox, 2); my $GID = $factory->createIntField($hbox, $self->loc->N("GID"), 1, 65000, $self->sh_users->min_GID); $GID->setEnabled($gidManually->value()); $gidManually->setNotify(1); $hbox = $factory->createHBox($layout); $align = $factory->createRight($hbox); my $cancelButton = $factory->createPushButton($align, $self->loc->N("Cancel")); my $okButton = $factory->createPushButton($hbox, $self->loc->N("Ok")); while(1) { my $event = $dlg->waitForEvent(); my $eventType = $event->eventType(); #event type checking if ($eventType == $yui::YEvent::CancelEvent) { last; } elsif ($eventType == $yui::YEvent::WidgetEvent) { # widget selected my $widget = $event->widget(); if ($widget == $cancelButton) { last; } elsif ($widget == $gidManually) { # GID inserction enabled? $GID->setEnabled($gidManually->value()); } elsif ($widget == $okButton) { ## check data my $groupname = $groupName->value(); my ($continue, $errorString) = $self->sh_users->valid_groupname($groupname); my $nm = $continue && $self->sh_users->groupNameExists($groupname); if ($nm) { $groupName->setValue(""); $errorString = $self->loc->N("Group already exists, please choose another Group Name"); $continue = 0; } my $gid = -1; if ($continue && $gidManually->value()) { if (($gid = $GID->value()) < $self->sh_users->min_GID) { $errorString = ""; my $gidchoice = $self->sh_gui->ask_YesOrNo({ title => $self->loc->N(" Group Gid is < %n", $self->sh_users->min_GID), text => $self->loc->N("Creating a group with a GID less than %d is not recommended.\n Are you sure you want to do this?\n\n", $self->sh_users->min_GID ) }); $continue = $gidchoice; } else { if ($self->sh_users->groupIDExists($gid)) { $errorString = ""; my $gidchoice = $self->sh_gui->ask_YesOrNo({title => $self->loc->N(" Group ID is already used "), text => $self->loc->N("Creating a group with a non unique GID?\n\n")}); $continue = $gidchoice; } } } if (!$continue) { #--- raise error $self->sh_gui->msgBox({text => $errorString}) if ($errorString); } else { Sys::Syslog::syslog('info|local1', $self->loc->N("Adding group: %s ", $groupname)); my $groupParams = { groupname => $groupname, is_system => $is_system, }; $groupParams->{gid} = $gid if $gid != -1; $self->sh_users->addGroup($groupParams); $self->_refresh(); last; } } } } destroy $dlg; #restore old application title yui::YUI::app()->setApplicationTitle($appTitle); } #============================================================= =head2 _buildUserData =head3 INPUT $self: this object $layout : layout in wich drawing graphic user data =head3 OUTPUT $userData: hash reference containing reference to graphical object such as: full_name, login_name, password, password1, login_shell full_name, login_name, password, password1, weakness (icon), login_shell =head3 DESCRIPTION This method is used by addUserDialog and _editUserDialog to create User Data dialog =cut #============================================================= sub _buildUserData { my ($self, $layout, $selected_shell) = @_; my @shells = @{$self->sh_users->getUserShells()}; my $factory = yui::YUI::widgetFactory; ## user 'full name' my $align = $factory->createRight($layout); my $hbox = $factory->createHBox($align); my $label = $factory->createLabel($hbox, $self->loc->N("Full Name:") ); $factory->createHSpacing($hbox, 2.0); my $fullName = $factory->createInputField($hbox, "", 0); $label->setWeight($yui::YD_HORIZ, 1); $fullName->setWeight($yui::YD_HORIZ, 2); ## user 'login name' $align = $factory->createRight($layout); $hbox = $factory->createHBox($align); $label = $factory->createLabel($hbox, $self->loc->N("Login:") ); $factory->createHSpacing($hbox, 2.0); my $loginName = $factory->createInputField($hbox, "", 0); $label->setWeight($yui::YD_HORIZ, 1); $loginName->setWeight($yui::YD_HORIZ, 2); $loginName->setNotify(1); ## user 'Password' $align = $factory->createRight($layout); $hbox = $factory->createHBox($align); $label = $factory->createLabel($hbox, $self->loc->N("Password:") ); my $weakness = undef; if (yui::YUI::app()->hasImageSupport()) { $factory->createHSpacing($hbox, 2.0); my $file = File::ShareDir::dist_file(AdminPanel::Shared::distName(), 'images/Blank16x16.png'); $weakness = $factory->createImage($hbox, $file); } else { $factory->createHSpacing($hbox, 1.0); $weakness = $factory->createLabel($hbox, " "); $factory->createHSpacing($hbox, 1.0); } my $password = $factory->createInputField($hbox, "", 1); $weakness->setWeight($yui::YD_HORIZ, 1); $label->setWeight($yui::YD_HORIZ, 1); $password->setWeight($yui::YD_HORIZ, 4); # notify input to check weakness $password->setNotify(1); ## user 'confirm Password' $align = $factory->createRight($layout); $hbox = $factory->createHBox($align); $label = $factory->createLabel($hbox, $self->loc->N("Confirm Password:") ); $factory->createHSpacing($hbox, 2.0); my $password1 = $factory->createInputField($hbox, "", 1); $label->setWeight($yui::YD_HORIZ, 1); $password1->setWeight($yui::YD_HORIZ, 2); ## user 'Login Shell' $align = $factory->createRight($layout); $hbox = $factory->createHBox($align); $label = $factory->createLabel($hbox, $self->loc->N("Login Shell:") ); $factory->createHSpacing($hbox, 2.0); my $loginShell = $factory->createComboBox($hbox, "", 0); my $itemColl = new yui::YItemCollection; foreach my $shell (@shells) { my $item = new yui::YItem ($shell, 0); $item->setSelected(1) if ($selected_shell && $selected_shell eq $shell); $itemColl->push($item); $item->DISOWN(); } $loginShell->addItems($itemColl); $label->setWeight($yui::YD_HORIZ, 1); $loginShell->setWeight($yui::YD_HORIZ, 2); my %userData = ( full_name => $fullName, login_name => $loginName, password => $password, password1 => $password1, weakness => $weakness, login_shell => $loginShell, ); return ( \%userData ); } #============================================================= =head2 addUserDialog =head3 INPUT $self: this object $standalone: if set the application title is set from the name set in costructor =head3 DESCRIPTION This method creates and manages the dialog to add a new user. =cut #============================================================= sub addUserDialog { my $self = shift; my $standalone = shift; if ($EUID != 0) { $self->sh_gui->warningMsgBox({ title => $self->name, text => $self->loc->N("root privileges required"), }); return; } my $dontcreatehomedir = 0; my $is_system = 0; ## push application title my $appTitle = yui::YUI::app()->applicationTitle(); ## set new title to get it in dialog if ($standalone) { yui::YUI::app()->setApplicationTitle($self->name); } else { yui::YUI::app()->setApplicationTitle($self->loc->N("Create New User")); } my $factory = yui::YUI::widgetFactory; my $optional = yui::YUI::optionalWidgetFactory; my $dlg = $factory->createPopupDialog(); my $layout = $factory->createVBox($dlg); my $userData = $self->_buildUserData($layout); ##### add a separator ## Create Home directory my $align = $factory->createLeft($layout); my $hbox = $factory->createHBox($align); my $createHome = $factory->createCheckBox($hbox, $self->loc->N("Create Home Directory"), 1); ## Home directory $align = $factory->createLeft($layout); $hbox = $factory->createHBox($align); my $label = $factory->createLabel($hbox, $self->loc->N("Home Directory:") ); $factory->createHSpacing($hbox, 2.0); my $homeDir = $factory->createInputField($hbox, "", 0); $label->setWeight($yui::YD_HORIZ, 1); $homeDir->setWeight($yui::YD_HORIZ, 2); # Create private group $align = $factory->createLeft($layout); $hbox = $factory->createHBox($align); my $createGroup = $factory->createCheckBox($hbox, $self->loc->N("Create a private group for the user"), 1); # Specify user id manually $align = $factory->createRight($layout); $hbox = $factory->createHBox($align); my $uidManually = $factory->createCheckBox($hbox, $self->loc->N("Specify user ID manually"), 0); $factory->createHSpacing($hbox, 2.0); my $UID = $factory->createIntField($hbox, $self->loc->N("UID"), 1, 65000, $self->sh_users->min_UID); $UID->setEnabled($uidManually->value()); $uidManually->setNotify(1); # $uidManually->setWeight($yui::YD_HORIZ, 2); $UID->setWeight($yui::YD_HORIZ, 1); ## user 'icon' $hbox = $factory->createHBox($layout); $factory->createLabel($hbox, $self->loc->N("Click on icon to change it") ); my $iconFace = $self->sh_users->GetFaceIcon(); my $icon = $factory->createPushButton($hbox, ""); $icon->setIcon($self->sh_users->face2png($iconFace)); $icon->setLabel($iconFace); $hbox = $factory->createHBox($layout); $align = $factory->createRight($hbox); my $cancelButton = $factory->createPushButton($align, $self->loc->N("Cancel")); my $okButton = $factory->createPushButton($hbox, $self->loc->N("Ok")); while(1) { my $event = $dlg->waitForEvent(); my $eventType = $event->eventType(); #event type checking if ($eventType == $yui::YEvent::CancelEvent) { last; } elsif ($eventType == $yui::YEvent::WidgetEvent) { # widget selected my $widget = $event->widget(); if ($widget == $cancelButton) { last; } elsif ($widget == $icon) { #remove shortcut from label my $iconLabel = $self->_skipShortcut($icon->label()); my $nextIcon = $self->sh_users->GetFaceIcon($icon->label(), 1); $icon->setLabel($nextIcon); $icon->setIcon($self->sh_users->face2png($nextIcon)); } elsif ($widget == $uidManually) { # UID inserction enabled? $UID->setEnabled($uidManually->value()); } elsif ($widget == $userData->{ login_name }) { my $username = $userData->{ login_name }->value(); $homeDir->setValue("/home/$username"); } elsif ($widget == $userData->{password}) { my $pass = $userData->{ password }->value(); $self->_checkWeaknessPassword($pass, $userData->{ weakness }); } elsif ($widget == $okButton) { ## check data my $username = $userData->{ login_name }->value(); my ($continue, $errorString) = $self->sh_users->valid_username($username); my $nm = $continue && $self->sh_users->userNameExists($username); if ($nm) { $userData->{ login_name }->setValue(""); $homeDir->setValue(""); $errorString = $self->loc->N("User already exists, please choose another User Name"); $continue = 0; } my $passwd = $continue && $userData->{ password }->value(); if ($continue && $passwd ne $userData->{ password1 }->value()) { $errorString = $self->loc->N("Password Mismatch"); $continue = 0; } if ($self->sh_users->weakPasswordForSecurityLevel($passwd)) { $errorString = $self->loc->N("This password is too simple. \n Good passwords should be > 6 characters"); $continue = 0; } my $homedir; if ($continue && $createHome->value()) { $homedir = $homeDir->value(); if ( -d $homedir) { $errorString = $self->loc->N("Home directory <%s> already exists.\nPlease uncheck the home creation option, or change the directory path name", $homedir); $continue = 0; } else { $dontcreatehomedir = 0; } } else { $dontcreatehomedir = 1; } my $uid = -1; if ($continue && $uidManually->value()) { if (($uid = $UID->value()) < $self->sh_users->min_UID) { $errorString = ""; my $uidchoice = $self->sh_gui->ask_YesOrNo({title => $self->loc->N("User Uid is < %d", $self->sh_users->min_UID), text => $self->loc->N("Creating a user with a UID less than %d is not recommended.\nAre you sure you want to do this?\n\n", $self->sh_users->min_UID)}); $continue = $uidchoice; } } my $gid = undef; if ($createGroup->value()) { if ($continue) { #Check if group exist if ($self->sh_users->groupNameExists($username)) { my $groupchoice = $self->ChooseGroup(); if ($groupchoice == 0 ) { #You choose to put it in the existing group $gid = $self->sh_users->groupID($username); } elsif ($groupchoice == 1) { # Put it in 'users' group Sys::Syslog::syslog('info|local1', $self->loc->N("Putting %s to 'users' group", $username)); $gid = $self->sh_users->Add2UsersGroup($username); } else { $errorString = ""; $continue = 0; } } else { #it's a new group: Add it $gid = $self->sh_users->addGroup({ groupname => $username, is_system => $is_system, }); Sys::Syslog::syslog('info|local1', $self->loc->N("Creating new group: %s", $username)); } } } else { $continue and $gid = $self->sh_users->Add2UsersGroup($username); } if (!$continue) { #---rasie error $self->sh_gui->msgBox({text => $errorString}) if ($errorString); } else { ## OK let's create the user print $self->loc->N("Adding user: ") . $username . " \n"; Sys::Syslog::syslog('info|local1', $self->loc->N("Adding user: %s", $username)); my $loginshell = $userData->{ login_shell }->value(); my $fullname = $userData->{ full_name }->value(); utf8::decode($fullname); my $userParams = { username => $username, is_system => $is_system, donotcreatehome => $dontcreatehomedir, shell => $loginshell, fullname => $fullname, gid => $gid, password => $passwd, }; $userParams->{uid} = $uid if $uid != -1; $userParams->{homedir} = $homedir if !$dontcreatehomedir; $self->sh_users->addUser($userParams); defined $icon->label() and $self->sh_users->addKdmIcon($username, $icon->label()); ### TODO Migration wizard # # Refresh($sysfilter, $stringsearch); # transfugdrake::get_windows_disk() # and $in->ask_yesorno($self->loc->N("Migration wizard"), # $self->loc->N("Do you want to run the migration wizard in order to import Windows documents and settings in your Mageia distribution?")) # and run_program::raw({ detach => 1 }, 'transfugdrake'); last; } } } } destroy $dlg; #restore old application title yui::YUI::app()->setApplicationTitle($appTitle) if $appTitle; } #============================================================= =head2 _createUserTable =head3 INPUT $self: this object =head3 DESCRIPTION This function create the User table to be added to the replace point of the tab widget. Note this function is meant for internal use only =cut #============================================================= sub _createUserTable { my $self = shift; $self->dialog->startMultipleChanges(); $self->get_widget('replace_pnt')->deleteChildren(); my $parent = $self->get_widget('replace_pnt'); my $factory = yui::YUI::widgetFactory; my $yTableHeader = new yui::YTableHeader(); $yTableHeader->addColumn($self->loc->N("User Name"), $yui::YAlignBegin); $yTableHeader->addColumn($self->loc->N("User ID"), $yui::YAlignBegin); $yTableHeader->addColumn($self->loc->N("Primary Group"), $yui::YAlignBegin); $yTableHeader->addColumn($self->loc->N("Full Name"), $yui::YAlignBegin); $yTableHeader->addColumn($self->loc->N("Login Shell"), $yui::YAlignBegin); $yTableHeader->addColumn($self->loc->N("Home Directory"), $yui::YAlignBegin); $yTableHeader->addColumn($self->loc->N("Status"), $yui::YAlignBegin); $yTableHeader->DISOWN(); $self->set_widget(table => $factory->createTable($parent, $yTableHeader)); $self->get_widget('table')->setImmediateMode(1); $self->get_widget('table')->DISOWN(); $self->get_widget('replace_pnt')->showChild(); $self->dialog->recalcLayout(); $self->dialog->doneMultipleChanges(); $self->_refreshUsers(); } #============================================================= =head2 _createGroupTable =head3 INPUT $self: this object =head3 DESCRIPTION This function create the Group table to be added to the replace point of the tab widget. Note this function is meant for internal use only =cut #============================================================= sub _createGroupTable { my $self = shift; $self->dialog->startMultipleChanges(); $self->get_widget('replace_pnt')->deleteChildren(); my $parent = $self->get_widget('replace_pnt'); my $factory = yui::YUI::widgetFactory; my $yTableHeader = new yui::YTableHeader(); $yTableHeader->addColumn($self->loc->N("Group Name"), $yui::YAlignBegin); $yTableHeader->addColumn($self->loc->N("Group ID"), $yui::YAlignBegin); $yTableHeader->addColumn($self->loc->N("Group Members"), $yui::YAlignBegin); $yTableHeader->DISOWN(); $self->set_widget(table => $factory->createTable($parent, $yTableHeader)); $self->get_widget('table')->setImmediateMode(1); $self->get_widget('table')->DISOWN(); $self->get_widget('replace_pnt')->showChild(); $self->dialog->recalcLayout(); $self->dialog->doneMultipleChanges(); $self->_refreshGroups(); } #============================================================= =head2 _refreshUsers =head3 INPUT $self: this object =head3 DESCRIPTION This method refresh user info into User tab widget. Note this function is meant for internal use only =cut #============================================================= sub _refreshUsers { my $self = shift; my $strfilt = $self->get_widget('filter')->value(); my $filterusers = $self->get_widget('filter_system')->isChecked(); my $usersInfo = $self->sh_users->getUsersInfo({ username_filter => $strfilt, filter_system => $filterusers, }); $self->dialog->startMultipleChanges(); #for some reasons QT send an event using table->selectItem() # WA remove notification immediate $self->get_widget('table')->setImmediateMode(0); $self->get_widget('table')->deleteAllItems(); my $itemColl = new yui::YItemCollection; foreach my $username (keys %{$usersInfo}) { my $info = $usersInfo->{$username}; my $item = new yui::YTableItem ( "$username", "$info->{uid}", "$info->{group}", "$info->{fullname}", "$info->{shell}", "$info->{home}", "$info->{status}" ); # TODO workaround to get first cell at least until we don't # a cast from YItem $item->setLabel( $username ); $itemColl->push($item); $item->DISOWN(); } $self->get_widget('table')->addItems($itemColl); my $item = $self->get_widget('table')->selectedItem(); $self->get_widget('table')->selectItem($item, 0) if $item; $self->dialog->recalcLayout(); $self->dialog->doneMultipleChanges(); $self->_refreshActions(); $self->get_widget('table')->setImmediateMode(1); } #============================================================= =head2 _refreshGroups =head3 INPUT $self: this object =head3 DESCRIPTION This method refresh group info into Group tab widget. Note this function is meant for internal use only =cut #============================================================= sub _refreshGroups { my $self = shift; my $strfilt = $self->get_widget('filter')->value(); my $filtergroups = $self->get_widget('filter_system')->isChecked(); $self->dialog->startMultipleChanges(); #for some reasons QT send an event using table->selectItem() # WA remove notification immediate $self->get_widget('table')->setImmediateMode(0); $self->get_widget('table')->deleteAllItems(); my $groupInfo = $self->sh_users->getGroupsInfo({ groupname_filter => $strfilt, filter_system => $filtergroups, }); my $itemColl = new yui::YItemCollection; foreach my $groupname (keys %{$groupInfo}) { my $info = $groupInfo->{$groupname}; my $listUbyG = join(',', @{$info->{members}}); my $item = new yui::YTableItem ("$groupname", "$info->{gid}", "$listUbyG"); $item->setLabel( $groupname ); $itemColl->push($item); $item->DISOWN(); } $self->get_widget('table')->addItems($itemColl); my $item = $self->get_widget('table')->selectedItem(); $self->get_widget('table')->selectItem($item, 0) if $item; $self->dialog->recalcLayout(); $self->dialog->doneMultipleChanges(); $self->_refreshActions(); $self->get_widget('table')->setImmediateMode(1); } #============================================================= =head2 _getUserInfo =head3 INPUT $self: this object =head3 OUTPUT $userData: HASH reference containing: username: username full_name: full name of user shell: shell used homedir: home dir path UID: User identifier acc_check_exp: account expiration enabling acc_expy: account expiration year acc_expm: account expiration month acc_expd: account expiration day lockuser: account locked pwd_check_exp: password expiration enabling pwd_exp_min: days before changing password is allowed pwd_exp_max: days before changing password is required pwd_exp_warn: warning days before changing pwd_exp_inact: days before account becomes inact members: Array containing groups the user belongs to. primary_group: primary group ID for the user =head3 DESCRIPTION Retrieves the selected user info from the system Note that acc_expy, acc_expm and acc_expd are valid if acc_check_exp is enabled. Note that pwd_exp_min, pwd_exp_max, pwd_exp_warn, pwd_exp_inact are valid if pwd_check_exp is enabled. =cut #============================================================= sub _getUserInfo { my $self = shift; my $label = $self->_skipShortcut($self->get_widget('tabs')->selectedItem()->label()); if ($label ne $self->loc->N("Users") ) { return undef; } my $item = $self->get_widget('table')->selectedItem(); if (! $item) { return undef; } my %userData; $userData{old_username} = $item->label(); $userData{username} = $item->label(); my $userInfo = $self->sh_users->getUserInfo($userData{username}); $userData{full_name} = $userInfo->{fullname}; $userData{shell} = $userInfo->{shell}; $userData{homedir} = $userInfo->{home}; $userData{UID} = $userInfo->{uid}; # default expiration time my ($day, $mo, $ye) = (localtime())[3, 4, 5]; $userData{acc_expy} = $ye+1900; $userData{acc_expm} = $mo+1; $userData{acc_expd} = $day; $userData{acc_check_exp} = 0; my $expire = $userInfo->{expire}; if ($expire && $expire != -1) { my $times = _TimeOfArray($expire, 1); $userData{acc_expy} = $times->{year}; $userData{acc_expm} = $times->{month}; $userData{acc_expd} = $times->{dayint}; $userData{acc_check_exp} = 1; } # user password are not retrieved if admin wants # to change it has to insert a new one $userData{password} = undef; $userData{password1} = undef; $userData{lockuser} = $userInfo->{locked}; $userData{icon_face} = $self->sh_users->GetFaceIcon($userData{username}); $userData{pwd_check_exp} = 0; $userData{pwd_exp_min} = $userInfo->{exp_min}; $userData{pwd_exp_max} = $userInfo->{exp_max}; $userData{pwd_exp_warn} = $userInfo->{exp_warn}; $userData{pwd_exp_inact} = $userInfo->{exp_inact}; if ($userData{pwd_exp_min} && $userData{pwd_exp_min} != -1 || $userData{pwd_exp_max} && $userData{pwd_exp_max} != 99999 || $userData{pwd_exp_warn} && $userData{pwd_exp_warn} != 7 && $userData{pwd_exp_warn} != -1 || $userData{pwd_exp_inact} && $userData{pwd_exp_inact} != -1) { $userData{pwd_check_exp} = 1; } $userData{members} = $userInfo->{members}; $userData{primary_group} = $userInfo->{gid}; return \%userData; } #============================================================= =head2 _getUserInfo =head3 INPUT $self: this object =head3 OUTPUT %groupData: selected group info as: $groupname: group name $members: users that are members of this group =head3 DESCRIPTION Retrieves the selected group info from the system =cut #============================================================= sub _getGroupInfo { my $self = shift; my $label = $self->_skipShortcut($self->get_widget('tabs')->selectedItem()->label()); if ($label ne $self->loc->N("Groups") ) { return undef; } my $item = $self->get_widget('table')->selectedItem(); if (! $item) { return undef; } my %groupData; $groupData{start_groupname} = $item->label(); $groupData{groupname} = $item->label(); $groupData{members} = $self->sh_users->groupMembers($groupData{groupname}); return %groupData; } sub _storeDataFromGroupEditPreviousTab { my ($self, %groupData) = @_; my $previus_tab = $self->get_edit_tab_widget('edit_tab_label'); if (!$previus_tab) { return %groupData; } elsif ($previus_tab eq $groupEditLabel{group_data}) { $groupData{groupname} = $self->get_edit_tab_widget('groupname')->value(); } elsif ($previus_tab eq $groupEditLabel{group_users}) { my $tbl = $self->get_edit_tab_widget('members'); $groupData{members} = undef; my @members; my $i; for($i=0;$i<$tbl->itemsCount();$i++) { push (@members, $tbl->item($i)->label()) if $tbl->toCBYTableItem($tbl->item($i))->checked(); } $groupData{members} = [ @members ]; } return %groupData; } sub _storeDataFromUserEditPreviousTab { my ($self, $userData) = @_; my $previus_tab = $self->get_edit_tab_widget('edit_tab_label'); if (!$previus_tab) { return $userData; } elsif ($previus_tab eq $userEditLabel{user_data}) { $userData->{full_name} = $self->get_edit_tab_widget('full_name')->value(); $userData->{username} = $self->get_edit_tab_widget('login_name')->value() ; $userData->{shell} = $self->get_edit_tab_widget('login_shell')->value(); $userData->{homedir} = $self->get_edit_tab_widget('homedir')->value(); my $passwd = $self->get_edit_tab_widget('password')->value(); $userData->{password} = $passwd; $passwd = $self->get_edit_tab_widget('password1')->value(); $userData->{password1} = $passwd; } elsif ($previus_tab eq $userEditLabel{account_info}) { $userData->{acc_check_exp} = $self->get_edit_tab_widget('acc_check_exp')->value(); $userData->{acc_expy} = $self->get_edit_tab_widget('acc_expy')->value(); $userData->{acc_expm} = $self->get_edit_tab_widget('acc_expm')->value(); $userData->{acc_expd} = $self->get_edit_tab_widget('acc_expd')->value(); $userData->{lockuser} = $self->get_edit_tab_widget('lockuser')->value(); $userData->{icon_face} = $self->get_edit_tab_widget('icon_face')->label(); } elsif ($previus_tab eq $userEditLabel{password_info}) { $userData->{pwd_check_exp} = $self->get_edit_tab_widget('pwd_check_exp')->value(); $userData->{pwd_exp_min} = $self->get_edit_tab_widget('pwd_exp_min')->value(); $userData->{pwd_exp_max} = $self->get_edit_tab_widget('pwd_exp_max')->value(); $userData->{pwd_exp_warn} = $self->get_edit_tab_widget('pwd_exp_warn')->value(); $userData->{pwd_exp_inact} = $self->get_edit_tab_widget('pwd_exp_inact')->value(); } elsif ($previus_tab eq $userEditLabel{groups}) { my $tbl = $self->get_edit_tab_widget('members'); $userData->{members} = undef; my @members; my $i; for($i=0;$i<$tbl->itemsCount();$i++) { push (@members, $tbl->item($i)->label()) if $tbl->toCBYTableItem($tbl->item($i))->checked(); } $userData->{members} = \@members; if ($self->get_edit_tab_widget('primary_group')->selectedItem()) { my $groupname = $self->get_edit_tab_widget('primary_group')->selectedItem()->label(); my $primgroup = $self->sh_users->groupID($groupname); $userData->{primary_group} = $primgroup; } else { $userData->{primary_group} = -1; } } return $userData; } #============================================================= =head2 _userDataTabWidget =head3 INPUT $self: this object $dialog: YUI dialog that owns the YUI replace point $replace_pnt: YUI replace point, needed to add a new tab widget $userData: hash reference containing user data info, tabs are removed and added again on selection, so data must be saved outside of widgets. =head3 OUTPUT $userDataWidget: hash containing new YUI widget objects such as: returned onject from _buildUserData and homedir. =head3 DESCRIPTION This internal method removes old tab widget saving its relevant data into userData and creates new selected table to be shown. =cut #============================================================= sub _userDataTabWidget { my ($self, $dialog, $replace_pnt, $userData) = @_; my $factory = yui::YUI::widgetFactory; $dialog->startMultipleChanges(); $replace_pnt->deleteChildren(); my $layout = $factory->createVBox($replace_pnt); my $userDataWidget = $self->_buildUserData($layout, $userData->{shell}); ## user 'login name' my $align = $factory->createRight($layout); my $hbox = $factory->createHBox($align); my $label = $factory->createLabel($hbox, $self->loc->N("Home:") ); $factory->createHSpacing($hbox, 2.0); $userDataWidget->{homedir} = $factory->createInputField($hbox, "", 0); $label->setWeight($yui::YD_HORIZ, 1); $userDataWidget->{homedir}->setWeight($yui::YD_HORIZ, 2); # fill data into widgets ## # full_name, login_name, password, password1, # login_shell $userDataWidget->{full_name}->setValue($userData->{full_name}); $userDataWidget->{login_name}->setValue($userData->{username}); yui::YUI::ui()->blockEvents(); $userDataWidget->{password}->setValue($userData->{password}) if $userData->{password}; yui::YUI::ui()->unblockEvents(); $userDataWidget->{password1}->setValue($userData->{password1}) if $userData->{password1}; $userDataWidget->{homedir}->setValue($userData->{homedir}); $replace_pnt->showChild(); $dialog->recalcLayout(); $dialog->doneMultipleChanges(); return $userDataWidget; } #============================================================= =head2 _groupDataTabWidget =head3 INPUT $self: this object $dialog: YUI dialog that owns the YUI replace point $replace_pnt: YUI replace point, needed to add a new tab widget %groupData: hash containing group data info, tabs are removed and added again on selection, so data must be saved outside of widgets. $previus_tab: previous tab widget label, needed to store group data from the old tab before removing it, if user changed something. =head3 OUTPUT %groupDataWidget: hash containing new YUI widget objects such as: groupname. =head3 DESCRIPTION This internal method removes old tab widget saving its relevant data into groupData and creates new selected table to be shown. =cut #============================================================= sub _groupDataTabWidget { my ($self, $dialog, $replace_pnt, %groupData) = @_; my $factory = yui::YUI::widgetFactory; $dialog->startMultipleChanges(); $replace_pnt->deleteChildren(); my $layout = $factory->createVBox($replace_pnt); my %groupDataWidget; ## user 'login name' my $align = $factory->createRight($layout); my $hbox = $factory->createHBox($align); my $label = $factory->createLabel($hbox, $self->loc->N("Group Name:") ); $groupDataWidget{groupname} = $factory->createInputField($hbox, "", 0); $label->setWeight($yui::YD_HORIZ, 1); $groupDataWidget{groupname}->setWeight($yui::YD_HORIZ, 2); $groupDataWidget{groupname}->setValue($groupData{groupname}); $replace_pnt->showChild(); $dialog->recalcLayout(); $dialog->doneMultipleChanges(); return %groupDataWidget; } sub _userAccountInfoTabWidget { my ($self, $dialog, $replace_pnt, $userData) = @_; my $factory = yui::YUI::widgetFactory; $dialog->startMultipleChanges(); $replace_pnt->deleteChildren(); my $layout = $factory->createVBox($replace_pnt); my %userAccountWidget; $userAccountWidget{acc_check_exp} = $factory->createCheckBoxFrame($layout, $self->loc->N("Enable account expiration"), 1); my $align = $factory->createRight($userAccountWidget{acc_check_exp}); my $hbox = $factory->createHBox($align); my $label = $factory->createLabel($hbox, $self->loc->N("Account expires (YYYY-MM-DD):")); $userAccountWidget{acc_expy} = $factory->createIntField($hbox, "", 1970, 9999, $userData->{acc_expy}); $userAccountWidget{acc_expm} = $factory->createIntField($hbox, "", 1, 12, $userData->{acc_expm}); $userAccountWidget{acc_expd} = $factory->createIntField($hbox, "", 1, 31, $userData->{acc_expd}); $userAccountWidget{acc_check_exp}->setValue($userData->{acc_check_exp}); $label->setWeight($yui::YD_HORIZ, 2); $align = $factory->createLeft($layout); $userAccountWidget{lockuser} = $factory->createCheckBox($align, $self->loc->N("Lock User Account"), $userData->{lockuser}); $align = $factory->createLeft($layout); $hbox = $factory->createHBox($align); $label = $factory->createLabel($hbox, $self->loc->N("Click on the icon to change it")); $userAccountWidget{icon_face} = $factory->createPushButton($hbox, ""); $userAccountWidget{icon_face}->setIcon($self->sh_users->face2png($userData->{icon_face})); $userAccountWidget{icon_face}->setLabel($userData->{icon_face}); $replace_pnt->showChild(); $dialog->recalcLayout(); $dialog->doneMultipleChanges(); return \%userAccountWidget; } sub _userPasswordInfoTabWidget { my ($self, $dialog, $replace_pnt, $userData) = @_; my $factory = yui::YUI::widgetFactory; $dialog->startMultipleChanges(); $replace_pnt->deleteChildren(); my $layout = $factory->createVBox($replace_pnt); my %userPasswordWidget; my $userInfo = $self->sh_users->getUserInfo($userData->{username}); my $lastchg = $userInfo->{last_change}; my $align = $factory->createLeft($layout); my $hbox = $factory->createHBox($align); my $label = $factory->createLabel($hbox, $self->loc->N("User last changed password on: ")); my $dayStr = $factory->createLabel($hbox, ""); my $month = $factory->createLabel($hbox, ""); my $dayInt = $factory->createLabel($hbox, ""); my $year = $factory->createLabel($hbox, ""); if ($lastchg) { my $times = _TimeOfArray($lastchg, 0); $dayStr->setValue($times->{daystr}); $month->setValue($times->{month}); $dayInt->setValue($times->{dayint}); $year->setValue($times->{year}); } $userPasswordWidget{pwd_check_exp} = $factory->createCheckBoxFrame($layout, $self->loc->N("Enable Password Expiration"), 1); $layout = $factory->createVBox($userPasswordWidget{pwd_check_exp}); $align = $factory->createLeft($layout); $hbox = $factory->createHBox($align); $label = $factory->createLabel($hbox, $self->loc->N("Days before change allowed:")); $userPasswordWidget{pwd_exp_min} = $factory->createInputField($hbox, "", 0); $userPasswordWidget{pwd_exp_min}->setValue("$userData->{pwd_exp_min}"); $label->setWeight($yui::YD_HORIZ, 1); $userPasswordWidget{pwd_exp_min}->setWeight($yui::YD_HORIZ, 2); $align = $factory->createLeft($layout); $hbox = $factory->createHBox($align); $label = $factory->createLabel($hbox, $self->loc->N("Days before change required:")); $userPasswordWidget{pwd_exp_max} = $factory->createInputField($hbox, "", 0); $userPasswordWidget{pwd_exp_max}->setValue("$userData->{pwd_exp_max}"); $label->setWeight($yui::YD_HORIZ, 1); $userPasswordWidget{pwd_exp_max}->setWeight($yui::YD_HORIZ, 2); $align = $factory->createLeft($layout); $hbox = $factory->createHBox($align); $label = $factory->createLabel($hbox, $self->loc->N("Days warning before change:")); $userPasswordWidget{pwd_exp_warn} = $factory->createInputField($hbox, "", 0); $userPasswordWidget{pwd_exp_warn}->setValue("$userData->{pwd_exp_warn}"); $label->setWeight($yui::YD_HORIZ, 1); $userPasswordWidget{pwd_exp_warn}->setWeight($yui::YD_HORIZ, 2); $align = $factory->createLeft($layout); $hbox = $factory->createHBox($align); $label = $factory->createLabel($hbox, $self->loc->N("Days before account inactive:")); $userPasswordWidget{pwd_exp_inact} = $factory->createInputField($hbox, "", 0); $userPasswordWidget{pwd_exp_inact}->setValue("$userData->{pwd_exp_inact}"); $label->setWeight($yui::YD_HORIZ, 1); $userPasswordWidget{pwd_exp_inact}->setWeight($yui::YD_HORIZ, 2); $userPasswordWidget{pwd_check_exp}->setValue($userData->{pwd_check_exp}); $replace_pnt->showChild(); $dialog->recalcLayout(); $dialog->doneMultipleChanges(); return \%userPasswordWidget; } sub _groupUsersTabWidget { my ($self, $dialog, $replace_pnt, %groupData) = @_; my $factory = yui::YUI::widgetFactory; my $mageiaPlugin = "mga"; my $mgaFactory = yui::YExternalWidgets::externalWidgetFactory($mageiaPlugin); $mgaFactory = yui::YMGAWidgetFactory::getYMGAWidgetFactory($mgaFactory); $dialog->startMultipleChanges(); $replace_pnt->deleteChildren(); my %groupUsersWidget; my $layout = _labeledFrameBox($replace_pnt, $self->loc->N("Select the users to join this group:")); my $yTableHeader = new yui::YTableHeader(); $yTableHeader->addColumn("", $yui::YAlignBegin); $yTableHeader->addColumn($self->loc->N("User"), $yui::YAlignBegin); $groupUsersWidget{members} = $mgaFactory->createCBTable($layout, $yTableHeader, $yui::YCBTableCheckBoxOnFirstColumn); my $users = $self->sh_users->getUsers(); my @susers = sort(@$users); my $itemCollection = new yui::YItemCollection; my $members = $groupData{members}; foreach my $user (@susers) { my $item = new yui::YCBTableItem($user); $item->check(MDK::Common::DataStructure::member($user, @$members)); $item->setLabel($user); $itemCollection->push($item); $item->DISOWN(); } $groupUsersWidget{members}->addItems($itemCollection); $replace_pnt->showChild(); $dialog->recalcLayout(); $dialog->doneMultipleChanges(); return %groupUsersWidget; } sub _userGroupsTabWidget { my ($self, $dialog, $replace_pnt, $userData) = @_; my $factory = yui::YUI::widgetFactory; my $mageiaPlugin = "mga"; my $mgaFactory = yui::YExternalWidgets::externalWidgetFactory($mageiaPlugin); $mgaFactory = yui::YMGAWidgetFactory::getYMGAWidgetFactory($mgaFactory); $dialog->startMultipleChanges(); $replace_pnt->deleteChildren(); my %userGroupsWidget; my $userInfo = $self->sh_users->getUserInfo($userData->{username}); my $lastchg = $userInfo->{last_change}; my $layout = _labeledFrameBox($replace_pnt, $self->loc->N("Select groups that the user will be member of:")); my $yTableHeader = new yui::YTableHeader(); $yTableHeader->addColumn("", $yui::YAlignBegin); $yTableHeader->addColumn($self->loc->N("Group"), $yui::YAlignBegin); $userGroupsWidget{members} = $mgaFactory->createCBTable($layout, $yTableHeader, $yui::YCBTableCheckBoxOnFirstColumn); my $grps = $self->sh_users->getGoups(); my @sgroups = sort @$grps; my $itemCollection = new yui::YItemCollection; my $members = $userData->{members}; foreach my $group (@sgroups) { my $item = new yui::YCBTableItem($group); $item->check(MDK::Common::DataStructure::member($group, @$members)); $item->setLabel($group); $itemCollection->push($item); $item->DISOWN(); } $userGroupsWidget{members}->addItems($itemCollection); $userGroupsWidget{members}->setNotify(1); my $primgroup = ''; if ($userData->{primary_group} != -1) { $primgroup = $self->sh_users->groupName($userData->{primary_group}); } my $align = $factory->createLeft($layout); my $hbox = $factory->createHBox($align); my $label = $factory->createLabel($hbox, $self->loc->N("Primary Group")); $userGroupsWidget{primary_group} = $factory->createComboBox($hbox, "", 0); my $itemColl = new yui::YItemCollection; foreach my $member (@$members) { my $item = new yui::YItem ($member, 0); $item->setSelected(1) if ($item->label() eq $primgroup); $itemColl->push($item); $item->DISOWN(); } $userGroupsWidget{primary_group}->addItems($itemColl); $label->setWeight($yui::YD_HORIZ, 1); $userGroupsWidget{primary_group}->setWeight($yui::YD_HORIZ, 2); $replace_pnt->showChild(); $dialog->recalcLayout(); $dialog->doneMultipleChanges(); return \%userGroupsWidget; } sub _groupEdit_Ok { my ($self, %groupData) = @_; # update last changes if any %groupData = $self->_storeDataFromGroupEditPreviousTab(%groupData); my ($continue, $errorString) = $self->sh_users->valid_groupname($groupData{groupname}); if (!$continue) { $self->sh_gui->msgBox({text => $errorString}) if ($errorString); return $continue; } my $groupInfo = { groupname => $groupData{groupname}, members => $groupData{members}, }; if ($groupData{start_groupname} ne $groupData{groupname}) { $groupInfo->{old_groupname} = $groupData{start_groupname}; } my $retval = $self->sh_users->modifyGroup($groupInfo); if (!$retval->{status}) { $self->sh_gui->msgBox({text => $retval->{error}} ); } $self->_refresh(); return 1; } sub _userEdit_Ok { my ($self, $userData) = @_; # update last changes if any $self->_storeDataFromUserEditPreviousTab($userData); my ($continue, $errorString) = $self->sh_users->valid_username($userData->{username}); if (!$continue) { $self->sh_gui->msgBox({text => $errorString}) if ($errorString); return $continue; } if ( $userData->{password} ne $userData->{password1}) { $self->sh_gui->msgBox({text => $self->loc->N("Password Mismatch")}); return 0; } if ($userData->{password} ne '') { if ($self->sh_users->weakPasswordForSecurityLevel($userData->{password})) { $self->sh_gui->msgBox({text => $self->loc->N("This password is too simple. \n Good passwords should be > 6 characters")}); return 0; } } if ($userData->{primary_group} == -1) { $self->sh_gui->msgBox({ text => $self->loc->N("Please select at least one group for the user")}); return 0; } my $userInfo = { username => $userData->{username}, fullname => $userData->{full_name}, homedir => $userData->{homedir}, shell => $userData->{shell}, members => $userData->{members}, gid => $userData->{primary_group}, lockuser => $userData->{lockuser}, }; $userInfo->{old_username} = $userData->{old_username} if $userData->{username} ne $userData->{old_username}; $userInfo->{password} = $userData->{password} if $userData->{password} ne ''; if ($userData->{acc_check_exp}) { my $yr = $userData->{acc_expy}; my $mo = $userData->{acc_expm}; my $dy = $userData->{acc_expd}; if (!_ValidInt($yr, $dy, $mo)) { $self->sh_gui->msgBox({text => $self->loc->N("Please specify Year, Month and Day \n for Account Expiration ")}); return 0; } $userInfo->{acc_expires} = { exp_y => $yr, exp_m => $mo, exp_d => $dy, }; } if ($userData->{pwd_check_exp}) { my $allowed = int($userData->{pwd_exp_min}); my $required = int($userData->{pwd_exp_max}); my $warning = int($userData->{pwd_exp_warn}); my $inactive = int($userData->{pwd_exp_inact}); if (!$allowed || !$required || !$warning || !$inactive) { $self->sh_gui->msgBox({text => $self->loc->N("Please fill up all fields in password aging\n")}); return 0; } $userInfo->{password_expires} = { exp_min => $allowed, exp_max => $required, exp_warn => $warning, exp_inact => $inactive, }; } $self->sh_users->modifyUser($userInfo); defined $userData->{icon_face} and $self->sh_users->addKdmIcon($userData->{username}, $userData->{icon_face}); $self->_refresh(); return 1; } # check the password and set the widget accordingly sub _checkWeaknessPassword { my ($self, $password, $weakness_widget) = @_; my $strongp = $self->sh_users->strongPassword($password); if (yui::YUI::app()->hasImageSupport()) { my $file = File::ShareDir::dist_file(AdminPanel::Shared::distName(), 'images/Warning_Shield_Grey16x16.png'); if ($strongp) { $file = File::ShareDir::dist_file(AdminPanel::Shared::distName(), 'images/Checked_Shield_Green16x16.png'); } $weakness_widget->setImage($file); } else { # For ncurses set a label $weakness_widget->setValue(($strongp ? $self->loc->N("Strong") : $self->loc->N("Weak"))); } } sub _editUserDialog { my $self = shift; my $dontcreatehomedir = 0; my $is_system = 0; ## push application title my $appTitle = yui::YUI::app()->applicationTitle(); ## set new title to get it in dialog yui::YUI::app()->setApplicationTitle($self->loc->N("Edit User")); my $factory = yui::YUI::widgetFactory; my $optional = yui::YUI::optionalWidgetFactory; my $dlg = $factory->createPopupDialog(); my $layout = $factory->createVBox($dlg); my %tabs; if ($optional->hasDumbTab()) { my $hbox = $factory->createHBox($layout); my $align = $factory->createHCenter($hbox); $tabs{widget} = $optional->createDumbTab($align); $tabs{user_data} = new yui::YItem($userEditLabel{user_data}); $tabs{user_data}->setSelected(); $tabs{used} = $tabs{user_data}->label(); $tabs{widget}->addItem( $tabs{user_data} ); $tabs{user_data}->DISOWN(); $tabs{account_info} = new yui::YItem($userEditLabel{account_info}); $tabs{widget}->addItem( $tabs{account_info} ); $tabs{account_info}->DISOWN(); $tabs{password_info} = new yui::YItem($userEditLabel{password_info}); $tabs{widget}->addItem( $tabs{password_info} ); $tabs{password_info}->DISOWN(); $tabs{groups} = new yui::YItem($userEditLabel{groups}); $tabs{widget}->addItem( $tabs{groups} ); $tabs{groups}->DISOWN(); my $vbox = $factory->createVBox($tabs{widget}); $align = $factory->createLeft($vbox); $tabs{replace_pnt} = $factory->createReplacePoint($align); $hbox = $factory->createHBox($vbox); $align = $factory->createRight($hbox); my $cancelButton = $factory->createPushButton($align, $self->loc->N("Cancel")); my $okButton = $factory->createPushButton($hbox, $self->loc->N("Ok")); my $userData = $self->_getUserInfo(); # userData here should be tested because it could be undef # Useful entry point for the current edit user/group tab widget $self->set_edit_tab_widget( %{$self->_userDataTabWidget($dlg, $tabs{replace_pnt}, $userData)} ); $self->set_edit_tab_widget( edit_tab_label => $userEditLabel{user_data}); while(1) { my $event = $dlg->waitForEvent(); my $eventType = $event->eventType(); #event type checking if ($eventType == $yui::YEvent::CancelEvent) { last; } elsif ($eventType == $yui::YEvent::MenuEvent) { ### MENU ### my $item = $event->item(); if ($item->label() eq $tabs{user_data}->label()) { $self->_storeDataFromUserEditPreviousTab($userData); my $edit_tab = $self->_userDataTabWidget($dlg, $tabs{replace_pnt}, $userData ); $self->edit_tab_widgets( {} ); $self->set_edit_tab_widget(%{$edit_tab}); $self->set_edit_tab_widget( edit_tab_label => $userEditLabel{user_data}); } elsif ($item->label() eq $tabs{account_info}->label()) { $self->_storeDataFromUserEditPreviousTab($userData); my $edit_tab = $self->_userAccountInfoTabWidget($dlg, $tabs{replace_pnt}, $userData ); $self->edit_tab_widgets( {} ); $self->set_edit_tab_widget(%{$edit_tab}); $self->set_edit_tab_widget( edit_tab_label => $userEditLabel{account_info}); } elsif ($item->label() eq $tabs{password_info}->label()) { $self->_storeDataFromUserEditPreviousTab($userData); my $edit_tab = $self->_userPasswordInfoTabWidget($dlg, $tabs{replace_pnt}, $userData ); $self->edit_tab_widgets( {} ); $self->set_edit_tab_widget(%{$edit_tab}); $self->set_edit_tab_widget( edit_tab_label => $userEditLabel{password_info}); } elsif ($item->label() eq $tabs{groups}->label()) { $self->_storeDataFromUserEditPreviousTab($userData); my $edit_tab = $self->_userGroupsTabWidget($dlg, $tabs{replace_pnt}, $userData ); $self->edit_tab_widgets( {} ); $self->set_edit_tab_widget(%{$edit_tab}); $self->set_edit_tab_widget( edit_tab_label => $userEditLabel{groups}); } } elsif ($eventType == $yui::YEvent::WidgetEvent) { ### widget my $widget = $event->widget(); if ($widget == $cancelButton) { last; } elsif ($widget == $self->get_edit_tab_widget('password')) { my $pass = $self->get_edit_tab_widget('password')->value(); $self->_checkWeaknessPassword($pass, $self->get_edit_tab_widget('weakness')); } elsif ($widget == $okButton) { ## save changes if ($self->_userEdit_Ok($userData)) { last; } } # last: managing tab widget events else { my $current_tab = $self->get_edit_tab_widget('edit_tab_label'); if ($current_tab && $current_tab eq $userEditLabel{account_info}) { if ($widget == $self->get_edit_tab_widget('icon_face')) { my $iconLabel = $self->_skipShortcut($self->get_edit_tab_widget('icon_face')->label()); my $nextIcon = $self->sh_users->GetFaceIcon($iconLabel, 1); $self->get_edit_tab_widget('icon_face')->setLabel($nextIcon); $self->get_edit_tab_widget('icon_face')->setIcon($self->sh_users->face2png($nextIcon)); } } elsif ($current_tab && $current_tab eq $userEditLabel{groups}) { if ($widget == $self->get_edit_tab_widget('members')) { my $item = $self->get_edit_tab_widget('members')->changedItem(); if ($item) { if ($item->checked()) { # add it to possible primary groups my $pgItem = new yui::YItem ($item->label(), 0); $self->get_edit_tab_widget('primary_group')->addItem($pgItem); } else { # remove it to possible primary groups $dlg->startMultipleChanges(); my $itemColl = new yui::YItemCollection; my $tbl = $self->get_edit_tab_widget('members'); for(my $i=0;$i < $tbl->itemsCount();$i++) { if ($tbl->toCBYTableItem($tbl->item($i))->checked()) { my $pgItem = new yui::YItem ($tbl->item($i)->label(), 0); my $primgroup = $self->sh_users->groupName($userData->{primary_group}); $pgItem->setSelected(1) if ($pgItem->label() eq $primgroup); $itemColl->push($pgItem); $pgItem->DISOWN(); } } $self->get_edit_tab_widget('primary_group')->deleteAllItems(); $self->get_edit_tab_widget('primary_group')->addItems($itemColl); $dlg->recalcLayout(); $dlg->doneMultipleChanges(); } } } } } } } } else { $self->sh_gui->warningMsgBox({text => $self->loc->N("Cannot create tab widgets")}); } destroy $dlg; #restore old application title yui::YUI::app()->setApplicationTitle($appTitle); } sub _editGroupDialog { my $self = shift; ## push application title my $appTitle = yui::YUI::app()->applicationTitle(); ## set new title to get it in dialog yui::YUI::app()->setApplicationTitle($self->loc->N("Edit Group")); my $factory = yui::YUI::widgetFactory; my $optional = yui::YUI::optionalWidgetFactory; my $dlg = $factory->createPopupDialog(); my $layout = $factory->createVBox($dlg); my %tabs; if ($optional->hasDumbTab()) { my $hbox = $factory->createHBox($layout); my $align = $factory->createHCenter($hbox); $tabs{widget} = $optional->createDumbTab($align); $tabs{group_data} = new yui::YItem($groupEditLabel{group_data}); $tabs{group_data}->setSelected(); $tabs{widget}->addItem( $tabs{group_data} ); $tabs{group_data}->DISOWN(); $tabs{group_users} = new yui::YItem($groupEditLabel{group_users}); $tabs{widget}->addItem( $tabs{group_users} ); $tabs{group_users}->DISOWN(); my $vbox = $factory->createVBox($tabs{widget}); $align = $factory->createLeft($vbox); $tabs{replace_pnt} = $factory->createReplacePoint($align); $hbox = $factory->createHBox($vbox); $align = $factory->createRight($hbox); my $cancelButton = $factory->createPushButton($align, $self->loc->N("Cancel")); my $okButton = $factory->createPushButton($hbox, $self->loc->N("Ok")); my %groupData = $self->_getGroupInfo(); # groupData here should be tested because it could be undef # %groupData: selected group info as: # $groupname: group name # $members: users that are members of this group # Useful entry point for the current edit user/group tab widget $self->set_edit_tab_widget( $self->_groupDataTabWidget($dlg, $tabs{replace_pnt}, %groupData) ); $self->set_edit_tab_widget( edit_tab_label => $groupEditLabel{group_data}); while(1) { my $event = $dlg->waitForEvent(); my $eventType = $event->eventType(); #event type checking if ($eventType == $yui::YEvent::CancelEvent) { last; } elsif ($eventType == $yui::YEvent::MenuEvent) { ### MENU ### my $item = $event->item(); if ($item->label() eq $tabs{group_data}->label()) { %groupData = $self->_storeDataFromGroupEditPreviousTab(%groupData); my %edit_tab = $self->_groupDataTabWidget($dlg, $tabs{replace_pnt}, %groupData ); $self->edit_tab_widgets( {} ); $self->set_edit_tab_widget(%edit_tab); $self->set_edit_tab_widget( edit_tab_label => $groupEditLabel{group_data}); } elsif ($item->label() eq $tabs{group_users}->label()) { %groupData = $self->_storeDataFromGroupEditPreviousTab(%groupData); my %edit_tab = $self->_groupUsersTabWidget($dlg, $tabs{replace_pnt}, %groupData ); $self->edit_tab_widgets( {} ); $self->set_edit_tab_widget(%edit_tab); $self->set_edit_tab_widget( edit_tab_label => $groupEditLabel{group_users}); } } elsif ($eventType == $yui::YEvent::WidgetEvent) { ### widget my $widget = $event->widget(); if ($widget == $cancelButton) { last; } elsif ($widget == $okButton) { ## save changes if ($self->_groupEdit_Ok(%groupData)) { last; } } } } } else { $self->sh_gui->warningMsgBox({text => $self->loc->N("Cannot create tab widgets")}); } destroy $dlg; #restore old application title yui::YUI::app()->setApplicationTitle($appTitle); } sub _editUserOrGroup { my $self = shift; # TODO item management avoid label if possible my $label = $self->_skipShortcut($self->get_widget('tabs')->selectedItem()->label()); if ($label eq $self->loc->N("Users") ) { $self->_editUserDialog(); } else { $self->_editGroupDialog(); } $self->_refresh(); } sub _deleteUserOrGroup { my $self = shift; # TODO item management avoid label if possible my $label = $self->_skipShortcut($self->get_widget('tabs')->selectedItem()->label()); if ($label eq $self->loc->N("Users") ) { $self->_deleteUserDialog(); $self->_refresh(); } else { $self->_deleteGroupDialog(); $self->_refresh(); } } sub _refresh { my $self = shift; # TODO item management avoid label if possible my $label = $self->_skipShortcut($self->get_widget('tabs')->selectedItem()->label()); if ($label eq $self->loc->N("Users") ) { $self->_refreshUsers(); } else { $self->_refreshGroups(); } # TODO xguest # RefreshXguest(1); } # TODO context menu creation is missed in libyui sub _contextMenuActions { my $self = shift; my $item = $self->get_widget('table')->selectedItem(); if ($item) { } } sub _refreshActions { my $self = shift; my $item = $self->get_widget('table')->selectedItem(); $self->dialog->startMultipleChanges(); $self->get_widget('action_menu')->deleteAllItems(); # do we need to undef them first? $self->set_action_menu( add_user => undef, add_group => undef, edit => undef, del => undef, inst => undef, ); $self->set_action_menu( add_user => new yui::YMenuItem($self->loc->N("Add User")), add_group => new yui::YMenuItem($self->loc->N("Add Group")), edit => new yui::YMenuItem($self->loc->N("&Edit")), del => new yui::YMenuItem($self->loc->N("&Delete")), inst => new yui::YMenuItem($self->loc->N("Install guest account")), ); my $itemColl = new yui::YItemCollection; for my $pair ( $self->action_menu_pairs ) { my $menuItem = $pair->[1]; if ($pair->[0] eq 'edit' || $pair->[0] eq 'del') { if ($item) { $itemColl->push($menuItem); } } else { $itemColl->push($menuItem); } $menuItem->DISOWN(); } $self->get_widget('action_menu')->addItems($itemColl); $self->get_widget('action_menu')->rebuildMenuTree(); if ($item) { $self->get_widget('edit')->setEnabled(); $self->get_widget('del')->setEnabled(); } else { $self->get_widget('edit')->setDisabled(); $self->get_widget('del')->setDisabled(); } $self->dialog->doneMultipleChanges(); } sub _manageUsersDialog { my $self = shift; if ($EUID != 0) { $self->sh_gui->warningMsgBox({ title => $self->name, text => $self->loc->N("root privileges required"), }); return; } ## TODO fix for adminpanel my $pixdir = '/usr/share/userdrake/pixmaps/'; ## push application title my $appTitle = yui::YUI::app()->applicationTitle(); ## set new title to get it in dialog yui::YUI::app()->setApplicationTitle($self->name); ## set icon if not already set by external launcher yui::YUI::app()->setApplicationIcon($self->icon); my $factory = yui::YUI::widgetFactory; my $optional = yui::YUI::optionalWidgetFactory; $self->dialog($factory->createMainDialog()); my $layout = $factory->createVBox($self->dialog); my $hbox_headbar = $factory->createHBox($layout); my $head_align_left = $factory->createLeft($hbox_headbar); my $head_align_right = $factory->createRight($hbox_headbar); my $headbar = $factory->createHBox($head_align_left); my $headRight = $factory->createHBox($head_align_right); my %fileMenu = ( widget => $factory->createMenuButton($headbar,$self->loc->N("File")), refresh => new yui::YMenuItem($self->loc->N("Refresh")), quit => new yui::YMenuItem($self->loc->N("&Quit")), ); $fileMenu{ widget }->addItem($fileMenu{ refresh }); $fileMenu{ widget }->addItem($fileMenu{ quit }); $fileMenu{ widget }->rebuildMenuTree(); my $actionMenu = $factory->createMenuButton($headbar, $self->loc->N("Actions")); $actionMenu->DISOWN(); my %helpMenu = ( widget => $factory->createMenuButton($headRight, $self->loc->N("&Help")), help => new yui::YMenuItem($self->loc->N("Help")), report_bug => new yui::YMenuItem($self->loc->N("Report Bug")), about => new yui::YMenuItem($self->loc->N("&About")), ); while ( my ($key, $value) = each(%helpMenu) ) { if ($key ne 'widget' ) { $helpMenu{ widget }->addItem($value); } } $helpMenu{ widget }->rebuildMenuTree(); my $hbox = $factory->createHBox($layout); $hbox = $factory->createHBox($factory->createLeft($hbox)); $self->set_widget( add_user => $factory->createIconButton($hbox, $pixdir . 'user_add.png', $self->loc->N("Add User")), add_group => $factory->createIconButton($hbox, $pixdir . 'group_add.png', $self->loc->N("Add Group")), edit => $factory->createIconButton($hbox, $pixdir . 'user_conf.png', $self->loc->N("Edit")), del => $factory->createIconButton($hbox, $pixdir . 'user_del.png', $self->loc->N("Delete")), refresh => $factory->createIconButton($hbox, $pixdir . 'refresh.png', $self->loc->N("Refresh")), action_menu => $actionMenu, ); $hbox = $factory->createHBox($layout); $head_align_left = $factory->createLeft($hbox); my $sysfilter = 1; if (-e $self->config_file) { my $prefs = Config::Auto::parse($self->config_file); $sysfilter = ($prefs->{FILTER} eq 'true' or $prefs->{FILTER} eq 'true' or $prefs->{FILTER} eq '1'); } $self->set_widget(filter_system => $factory->createCheckBox($head_align_left, $self->loc->N("Filter system users"), $sysfilter)); $factory->createHSpacing($hbox, 3); $head_align_right = $factory->createRight($hbox); $headRight = $factory->createHBox($head_align_right); $factory->createLabel($headRight, $self->loc->N("Search:")); $self->set_widget(filter => $factory->createInputField($headRight, "", 0)); $self->set_widget(apply_filter => $factory->createPushButton($headRight, $self->loc->N("Apply filter"))); $self->get_widget('filter')->setWeight($yui::YD_HORIZ, 2); $self->get_widget('apply_filter')->setWeight($yui::YD_HORIZ, 1); $self->get_widget('filter_system')->setNotify(1); my %tabs; if ($optional->hasDumbTab()) { $hbox = $factory->createHBox($layout); my $align = $factory->createHCenter($hbox); $self->set_widget(tabs => $optional->createDumbTab($align)); $tabs{users} = new yui::YItem($self->loc->N("Users")); $tabs{users}->setSelected(); $self->get_widget('tabs')->addItem( $tabs{users} ); $tabs{users}->DISOWN(); $tabs{groups} = new yui::YItem($self->loc->N("Groups")); $self->get_widget('tabs')->addItem( $tabs{groups} ); $tabs{groups}->DISOWN(); my $vbox = $factory->createVBox($self->get_widget('tabs')); $align = $factory->createLeft($vbox); $self->set_widget(replace_pnt => $factory->createReplacePoint($align)); $self->_createUserTable(); $self->get_widget('table')->setImmediateMode(1); $self->get_widget('table')->DISOWN(); } $self->_refreshActions(); # main loop while(1) { my $event = $self->dialog->waitForEvent(); my $eventType = $event->eventType(); #event type checking if ($eventType == $yui::YEvent::CancelEvent) { last; } elsif ($eventType == $yui::YEvent::MenuEvent) { ### MENU ### my $item = $event->item(); my $menuLabel = $item->label(); if ($menuLabel eq $fileMenu{ quit }->label()) { last; } elsif ($menuLabel eq $helpMenu{about}->label()) { my $translators = $self->loc->N("_: Translator(s) name(s) & email(s)\n"); $translators =~ s/\/\>\;/g; $self->sh_gui->AboutDialog({ name => $self->loc->N("AdminUser"), version => $self->VERSION, credits => $self->loc->N("Copyright (C) %s Mageia community", '2013-2014'), license => $self->loc->N("GPLv2"), description => $self->loc->N("AdminUser is a Mageia user management tool \n(from the original idea of Mandriva userdrake)."), authors => $self->loc->N("

Developers

Translators

", "Angelo Naselli <anaselli\@linux.it>", "Matteo Pasotti <matteo.pasotti\@gmail.com>", $translators ), } ); } elsif ($menuLabel eq $self->get_action_menu('add_user')->label()) { $self->addUserDialog(); $self->_refresh(); } elsif ($menuLabel eq $self->get_action_menu('add_group')->label()) { $self->_addGroupDialog(); $self->_refresh(); } elsif ($menuLabel eq $self->get_action_menu('del')->label()) { $self->_deleteUserOrGroup(); } elsif ($menuLabel eq $self->get_action_menu('edit')->label()) { $self->_editUserOrGroup(); } elsif ($self->get_widget('tabs') && $menuLabel eq $tabs{groups}->label()) { $self->_createGroupTable(); } elsif ($self->get_widget('tabs') && $menuLabel eq $tabs{users}->label()) { $self->_createUserTable(); } elsif ($menuLabel eq $fileMenu{refresh}->label()) { $self->_refresh(); } } elsif ($eventType == $yui::YEvent::WidgetEvent) { ### Buttons and widgets ### my $widget = $event->widget(); if ($widget == $self->get_widget('add_user')) { $self->addUserDialog(); $self->_refresh(); } elsif ($widget == $self->get_widget('del')) { $self->_deleteUserOrGroup(); } elsif ($widget == $self->get_widget('table')) { $self->_refreshActions(); my $wEvent = yui::YMGAWidgetFactory::getYWidgetEvent($event); if ($wEvent && $wEvent->reason() == $yui::YEvent::Activated) { $self->_editUserOrGroup(); } } elsif ($widget == $self->get_widget('add_group')) { $self->_addGroupDialog(); $self->_refresh(); } elsif ($widget == $self->get_widget('edit')) { $self->_editUserOrGroup(); } elsif ( $widget == $self->get_widget('filter_system') || $widget == $self->get_widget('refresh') || $widget == $self->get_widget('apply_filter') ) { $self->_refresh(); } } } $self->dialog->destroy() ; #restore old application title yui::YUI::app()->setApplicationTitle($appTitle) if $appTitle; } #============================================================= =head2 _skipShortcut =head3 INPUT $self: this object $label: an item label to be cleaned by keyboard shortcut "&" =head3 OUTPUT $label: cleaned label =head3 DESCRIPTION This internal method is a workaround to label that are changed by "&" due to keyborad shortcut. =cut #============================================================= sub _skipShortcut { my ($self, $label) = @_; $label =~ s/&// if ($label); return ($label); } sub _ValidInt { foreach my $i (@_) { $i =~ /\d+/ or return 0 } return 1; } sub _TimeOfArray { my ($reltime, $cm) = @_; my $h; my %mth = (Jan => 1, Feb => 2, Mar => 3, Apr => 4, May => 5, Jun => 6, Jul => 7, Aug => 8, Sep => 9, Oct => 10, Nov => 11, Dec => 12); my $_t = localtime($reltime * 24 * 60 * 60) =~ /(\S+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\d+)/; $h->{daystr} = $1; $h->{month} = $2; $h->{dayint} = $3; $h->{year} = $5; $cm and $h->{month} = $mth{$2}; $h; } no Moose; __PACKAGE__->meta->make_immutable; 1; Z+_7|Yr5g)g^> 8ܼg0<1ٙk:<ӏUkM)1{xNO G&Ӵk=sրx7 єsTn%TM©(mzqn`yN{[h< Pt~啀L[ &\dYHHeiLǡ/ܱJ— &VEJMK!C\Etfgk.~g׊D'MC@LZXN!Pf߫xS9Ht luUt_jePxݷ6m\G6^Jh:AEPu֏iOgORK!loQì%E͌=$z`,jČ*: ?!>ہl/^JE4pvgٴ C  Ţ4b[oCt H1/`"vZf܋d)pҪb/9 $BPAFhn3Hpؘ'} ëVBAHpr4:el +:.K6jJo?p2LEB mJG_׮Nhzȥrf Q'a}3: E d?,D u)ډ 9ÅH42ݪh7hsX&gn`DHtjy`9 !\l_ډX%t%+?y2V@8y` 3فR4iaa JQrv$^EHgڿ OÀ`F?"hgd2Scx>㒾C @@`Ѩ|K=fh|a4ᗔC>Њw0.nc"qicBD!n0@b PUr V\t`14Pv/!71$ H`\(g L3Y2qSyp"%?>%TV.x?snP8_-o wv@;,eR?;&N40@|GïQEѣQVFU>V_FcHBi)]B8](L0M[BAIAIAIAACT5AIAIAIAAHdސq xA # H"r\ @pCXouUQUUUUUUUUCEUUSM5TU}+HB!DAQ3 T!};`<{PMBK 0s篟eqBŅU{'<',Q]L6?%E`CC@BqA |JX$!}`ڞ^∟12A"5h E뷜pC29d`Oe 9.z,{hn?μ\Uv,5Wu-qe|E/)6RTLеLiC&=b!y1EJz _Qֲn|gwv+V<"uFlsڔ6M~47hB_7ްԜ ]̱L2lytp4VƜͬ&H8tc'0YC`T Ւw:ot~?\e"AXÇ%AFUUY_InӆĐPyei9jN6:U[3 r\6mSx7k_J^遻FފWî%f(Ȥ!A98+VB2qMݖ$m}ָίrULTu0&hja?{UUU[N~zNW4lc$"3tBkCXCK U>/H@О>$Kjڬ"e|9ܳEQE_>ux +(c#zA`'*cUZ4)p' PA@|*}EбS%4Qf?ZO'd+SpHa}!J_ǛA E/<{WkoB싐$`ʢ9*~q,ݶpLme[;XH J 1?ͪ<r*,OjFۆ\38f;l]**3WXVc~[r6|QLFL1xƨkm8t㓍ϧ)ƔdJܐM U uv%]?M0`{@O4&+!PU-U Z,ɤ@5̙Pv8iU#j=6s痩-$kj H',NƐ_km,aaxGfQِN 1BC1Ϩ넒BBJ'סﶢm7x B*!gO0g$ 5m[UuA^6{Ĺs9C#xxÞ />`LrvʒiD>y#E \6pR}JZDAiw7:BN^?/r!"oG 7ӯjo%jh|&֔0 2I >zdmK_"$q8!tyQR~$N(?ICҮ<4A,l!_3GF} }#?;Eޠ"Cf|uq; ]+ۜinO{\15`7$!+C: I'c7D9j,jC0zA@:Rͨ jn"qY5ڦ5`:H-3̋ZFcv[_Z돞 G\o'g7>gPk"0v0HQnĤ!, '-s=%.& ll`!4K65f: fc+KpHJ\(hr[ڮ4qp ĀnCq4q4lr`qK9…O2ƸJ. u0@!p>g`Ay«DbW8X wjcX, ;1#@b=HڇTp|q#&)%l•T{o.IK'#vdai GgܤLi  {^BH> uǛ1RA9)91%Hwޤ t;_ LE(j79qzع|p Ѳ F޽R IX!e14q9)1P!_3&Tf :{fI:uiSȏQL>&GiH%CHezO=1ǰLX{/b@vZh:zABDɱcZsO/I#F؝nV`㵞dY4)vgZ$ż1LؚH-b!٫:Zsxmp9䙛Q5D t2sh.ְ#N!J{ԧUDZqʹ|Hg*'`u%x)v9H:\>πV`څle bXa؆lp q 8\|̭"{s4ƒ=_^,`>0UXt:6X˃`aip 16pJ( ˪ β=qgq2TwQiVr{ÄΓbY|,̐*jHBm.!"tnSpBQLi Lx4hbT!PUq(j~0?w`@:s0-:vp<'S.v&@5.qC")9eʐxxx R 2\ a#,t#04"*dRՇ!ie%hOĠM#"N58bhu[d 537ܞ*rvήJ) |IDۆd.z6f$IDie o _9h wY oއwwPQLH|:Q_18cb=uY/CNBqf;d:nJ= kq)4ٹǽacK܄ }_~dB~lk!p`3ͤ"@!SLE 믿L=âQ|2Aȋv6G[`*q TDDt !QOsM *,CkjCUQ]C5Jad0 ;Wv[d8cUF9HʐcNyzh=k ~`0m0dh8n& _T"H?~16^!96(WLWp'G$| CM'G=̌LZ%F;?&'2`BE]aI-&-g[ -& pNIL,~e"9+OYJ95<0⛐X?9AA%'BN6s1+SpuW2x)/>G MXI$UŸ'+ݚXXf`a+p "j"?'}8 d:xu ~~GPX(ur^)AAl&=1tƖL@`cseHh1=5i:c1d"`C0Y8z @0~?-ߙTGw/ܷ\ @,ZNk_ƅ: XKcb۾ARm6Ԋf0 x5X>Ke<bT0aUUID3 e~a 43c]JBkLa*eg7Z*u *h4~dQݾH)pdWMa>޿݄%g7Oxl;9fZw8"R Xt\D94S=/xge,M)UՒe4X$aln_5-+ݣ-ZͺDd>37[8Di5*.ENN4{K" +">oME(QO=]u!X ~ŝyFJ,,JƿL2L/6 (CM?J0wìF! )IU(7bj*7`~L("#lxwyt;XϽD0?>#ADEQEQDDQEQDRd@2|G/|Ƕm˪22Tddg#7uWOnc fcR;B?NPm\&T `ܒ ꑢ9F16PCzQ8 0ڏmg&"jg !$=y/7b|#Ď?LGM}V7P<'RҔ-t5ȣ|EO0R! f?낎>D&F?gkEpĄMs4$(9hkv~}[6vM/~~mYemmuU]uR]u]u]u]u]vtğ FRq뭺ۮFUKnU!FUKekU!FUKn1)HFRۭ뮺mۡFUKF뮪R뮺jTVVR뮺뮺jTq1'?q;4>BBd!QH2A$|Q{P3' JOniOMN4h5z583*sONNVtF }sbPq2QW2f?#57&гD2$ g$hޭF}}SG\jo!\/ RT[`A9(c:$l>DUEDAMg8-c#3۵  9Rci` M_BldٶZAm"x2F4-x^ P%ȭhT !t@$B-A% rC GV@>xL\b6/YQL!&5t9:ˏE7tҒeB3fLx@y:".ӞJ%ވsԺ2 cBddx첵r1dA%6&2/]T@8ʇizG۵ܶ4&q*^9 9<љPݏ?0h1ݬ`%!s'Gr#@P- Jvl?ޓAR%x< b D  /WOG<5u|;;{;p=_ܻp!D$ju]ָvWUoU!5u>_tG ,TI!#5:1s!.]AD+XA[k}Hbl0'!b~@?F,[kNpnU03n/ jK? 20mj [G R 4S}!ɱ ?긛4Z#\zBt}Y'4}X}}/n^ I$<⑊HTs7wMxzVVm! @T|.Nr .xB]*=gt@2~-h":j۱)Y[p>9)U_Z{ fveZZ06A6oƄ hI g$,;b"@9OD:qf" m`6iB{b.xWrhu:#Kr `F+ 3p~ ˅)hE(?*<́=>FJ (@;Շt-!`"WpB BH HgN9SB~ )2foX k@ExYϳ;! !Y-C*\0\A$ʙGӟ-p^5%|R4֕G㰇 r̭zjaM<<,[W_ժA=9; .0cu&"{dBO!BB\3ZL]cA @uE AW9#J}%8a ]X=πd:eQ&Hq 4]A(njR0rzs%3jރ]d;GjـJjŝSa7ɩu@7G5пSrqP} @=2 -OOǡ=G݃i ifm_|Fqs  u۳^:Nᨰjщ>>>8ž@ֳo Q3g t#yC0 i)VY5^v!,A08C&eib ^1ʚrH>T8]#ǀ>B&yS BAY^*$XMwvV"* BOXJp;*7V9|2n AG\h\ uZܘ]NcUM!E[ x끥]PG^.X3`$؛|)p=Su K 6a5S-C8'ז)B0i{wšJQ#!$8؉MҮ,f@Fayp4/ldszAۨC6ܨhHޕ6_װE ALZ sђ1~Z|N zyT h߬QϷ~..$mJ[;1|9wl-&3X | BHܔ`t4Oˤu 'K+`O᯷V켌7$39VF}T72;5x|G¾87Iz!j@] ! 쨫p}}ܾQrQGq\Qd>l:BR$5\ (n|ۨG`қf8;Suhm !C`| M^@o灾PE`zAzO_>ӌr؆A'l5]h5F1K0qa: ѭ«d X&U-mQTpD*ܺNE=[ ڋN:vr#%iO^pdIj) M˜Bve˒T o61\L(c2r) H?{?Lyh|vYT(!dSW@3!p *y7|g`?@ކt|@s#h|F|:SzH̱,Ʋs ?<4֨Pr0)K@?8'zw- reN]rIoT5Ň҇5^K2 PВRrWniو^Ia:oх>< @ 0H7 E:WŨPչL!cwUUUUUUUUUUUUUUUUUUUUUUUUWeCc3{7݋qU/}@wg] :FKE kL0D@v;#RBJSCp!@!6SD[ujث#dhLȳބ(BI$cH֠kAhjdJgE_9o=0SX A) h$F G##Qe0 8.qԜH q~Eo`o٥L'hK(\h>Xbo SoⶆJlHl ˥᧑|pc4 fDPL] Iy@ 5#0 (ͬ~T{as/RŃ@amul l!&'8 q:ht߻ ;nq 5˚Svw(S+KÞ*fH%ϕ -oMFӴ{j9H. c{o5h m.M0Z*IBT%S W([q~3n<1|xBn28ar<եb/90iRHN0a% ӸqI߻TCl%eLn@œAN\`$t  {At,m8t{RfPyiZ  [.H.p5! gn REV9%-d_#>u B%S3E;#gۉP+ іc[:> yI>iC q%+LKa> (+d1xcQtGs1pD=ul넥a}QN:pu " 3gy q qML?Oe_1qUI&iPĮs`ŒGY C ;3kja3U((;N20_3/]iRiq_oXywxp9x-o-8!`%A̩(qSXUB辶<`tswZY(=Q Y( 'D'@îc@P뭚rWע@=&HijH@K :^#Ӓ ]]ü-Chhx'b@]{&havi(fJz,zT咦LTܔK-y@~a@a J{D5HPo4h"BzhB@cPի,tǙXUL,ad!CT kv*1 Ōhb<ƅWVfp8 8 2W 4Q#d_ !]y"0d'3SC B3#;eHqvw"a!` oq[c+_/WF> r3Qjxuɣd'}T,#.C:T>0Frj>O!'qaA"kAF)W;)p( FP]3^<XE*)þ'}bц_VOɶM=݀ SNIKO,qp3S+s+2driGPR*#` |#Q=^y.J_߳ځ>tGapQBwP3Īq{wZ@L[t,Pf#D葀-sIB d.y`u:~WOz $xw  aHUR8ތxu*k!b e"5 '8ܧ1zgf1-9`[K& f֗P#X!}{yGŬ`TQ*ml qN"|\&A5PD0Kh8y"; (:@1h\v$_`` @ЇB<}kxʩfJDų.]Mٞڥ>ްM*"i`.X(Ću&ͦQa sH|k" J@E,1C=i0EC:g1dxb4&JN7BD%@DNc 0tJ!u\; ~].u"P=<%cGQc@xXn->"T6Bm9t)nr燐P@A-LAu.fB/ؾ'[ɳ7|ͅDUȕ̝IHY$ngI8Q }@D_=/3Pq xu b' b*fx 3 {qfE; )e^P! )t+lHh  Cc= -L-@ G\;B94/ǧ&('IbF 豋wkde5"KNE` .i, 㮓}ajsihlӞ^o)pqgN[W )PQ4jeE]hٙ6)MHc'xHPVPhӘÂ0d LbLJcu 624-Fl`p[(F4Ht\刖./o7֔P2Y& BPr&d Yb4Jū%_7#>j# dV럞:lS?Gb[9C=THIUgzDvfnSLԤ'lE_?"Rz% 2/è!Q pIS"౷P3Hx,bx1^Izgq Q1ʀG ;ci /P:>: 'XlF>7뮙gdyPt ~>QKVb1>ѯN<8iAO!^cBBi7q(1ds\ ވ  WGFQ6uc@hJ}_W 7]h1JEbDc13`D:Y& K;ַ5bvrE'3C*@;Ck$@X' 9vn{|Lm΃I i&0TP[@6L(P0 pv%s4n϶N=Wcw Aj\M4>`_6ɵrMk`2R~Goec>??|P4.]}ԞNا|hQrx8AGr Џ=۹>X0lXhhyzڙu+8`vb]?T oC}݁Am)$`e,%e'4!F#8 ArWVsz~on: pBPfss;((ղ 9ӡXnOy yRF#4s μ>E.VS_Ѝ 3pϟ !)eӝtB:7;L&]lc7JY$epe@#?R"A!tCZq42?41DJqHPV#/yԼ苕^ V*!SZ"mVHv0 Rgq6PdNf%Hqʱ޶(rz:dXffsM(fn6*  FrG"lB#K)IE!颯*;=2&B q(DxҁddV_#DM0P]1'+zHceΓ#:!Y `z<,/c뢯60޶Fkk m@(bR6vF=hIR9 {êj*?eIGwy٦֜-R}5걥e;;iINOdq-C!ּT=cMғɰη9c&^3ܷzZ0Qpsl].uPdI4+E^䄷bU5+Z$D;TmFק7mbu9Y&jCث J IP`ּ8Hh/b4yR%O<'%x\^;?I??Aph~Dp__~o爛irg=#3w??Kڛf3u7_ֿgʋֳuv խ t./8z?Zg;8e?M.۩Wfys)(Pw"v+} y8uP?}f}"u0OMVUݴMO ףO$wٍn5\zDlNv1箂w9"sQW~-!hY_6穊 ƽY6ðA-t0>_^e7Wf~OĚa VpƔ66mYUeV9X8D-eh ݭY]zm/ ' dܣ2 XOeB#we*V 9k=F[hlim$I9yuE냬ꂺ!$A#6޷~qC8t:56G TP63 IP/Uzu\éY[GaxK!r ;pаlCjZZ<"_ uf? qahexŴ@Լmd;^ϘL$ Q EmbѤN}h7%e8~Nӌ9zl IH8VX [> 1YK#u(Њ$aJo/f}P1XqL$ 1Ծ^H曆hՋLA?-[ucV2ƈ&2B!AEt?EA_S]6ׇ_Of5*Q@1!kmcSc8&,pOK8ݑ'p÷ $w "cX82Œ=`p}D!>kvGO ں7NjXJ'toYp/mXxqθ `{:NnܔXmܔ}3xk^ 4a G}i6D/㣍/8 HP pMo-p`ȰxB҃v8u: z 9ߋy5@ܫp[r#J8c 0亹ћ%3B|ZZ(-q/sZĔ:,h7Mf%sunLt8ϴlJ &zou [!].:l JK ƤE=-f('՟lӿa5&H z[M~pzJvm5sߢηva;\]#ѿTu䙆Na4dt'}+́]2zaUV~OYLiifsOnQxBY)NRݳwC0@RA $"RN8P8­5 [g=]>ᯩ;~=c=-,UX 82m'S/g!}%hL_+:.nw۞>rG#j:噲QYiK/͢IӘ[?Kw=f_\sD3 ]LNhk>yi 4AEؐ򺽺 8 u (tH:I;ۦlYС-=6ucq,u1ss yC~ti;GAno@y!/<ܐwN64zZ*uEQ&u9 Zޞv0lso+pLJF`BcME-%+M{0Ј+xݺ_c !l@R߉HZ;N\E?9 X(*HЉAsp eU>Fiw!8SBj'WHneCw-X$$Kv=w`+pq<elLY |]KR!ꔪiL{s܈9AZ͝iq@0q͑&8 xOA- |\ =!pq D@Q51Dd]0z5AnrI{DY?;܉dp43S4ׁDZ`p`j9f80,SY169iq \ l)~|,#`ѧvkTsrfF%`rrdnCNgPKN˫xH:R6=P٠zF#rN)a 0| 6'I=5m0l|Z_1y wm4ߴ$ qZ61dsϮ ;vQ].,"ڂ'"A} BlHg$>)"2$p}>~-nnB}/}ޗ y&fAW:MGk sSgAM[_RDnE؃N!m&>_7?q,  *`a_'=o9Gx{ %wU0U ֐9<Ȏ&M2 cV7^ o! \zbG*D6+cftVG1ee^^Бs^}Eˆi[z[f :7;ѵo`Fo5(KauHC1/&1Gs.n`` o>:dV9uLR;2FI@No2NfAÞE֡!ـVvE;‰.*cA bvta5#^z[:}gS\t>,3~r:4f9.;DEammjB!:ր`tMG@zȝ0)f[âB\)xw"a tnF'^[u30V}W鋾_gOoۦ4Uo[CA24P5A#[Phjh&"b3v d$+`͜p\kܱ+3B-IˀFÞv!7ivcd5rN TOۉ92gY)mvh tEAX3S iՍ >:趞T0VYv?۸㜒Hu jKS 6[ RsRP= n xp e6 ݜ{^S" ;Iu@6f ֭%:$sRdglGмkTaJDg6q[ak[nonܯǠC@HM #~QϛQ)vkvf\i1C{.1& `Zk'DN3AP oeA|2avXDu Dq(]UnKgow RQN' <9CxGbftC>x6`5^Б_zFfehGֆ ˽i$"RZJ-g)ofzR|L~lϿ(%f6%`0N Aׁ/ EaQaBHGÙ3TjrŹ8z߽4ugwbuMu`6ԗ`k2iV&7=.:`po$@pKɟ/N6 ̟ :`@s=PN|avH"GSA8lɰWVF,҂ޏJo,o6ًǮBI#DQ\̊3O4ɚ׊ɼ8,~#/l9AcΜc`1+!nhv% [VzUpLzc=;Jr"\׉/y=>.4!i^mgVl }PA DDC8{o{QǙ6m57E1n_""6RC̓ZÖD4f, _o03d,$c`>2+/b+v #,F2Q8A+DUX4<ˢFNР4Y|U NuMvz,p(.EzW hqhzX`SA~yZFXĊ1z(J|!oG K=#,`.UoS;hhZ2ykqD8l&aS[)`2`{o 8f,,;@YTrf/4jjqm=HBnø6Lc[,4-ٙC%ڐy($֔52Ձj6)7u;@H/8dt쥺8;&-Wpfv(a~olnHČ^9TJIs.L `e XC6p hsU0 #"s!%ԅ8s:ae8M4Gy@U2_AfʀHUD ̄tbz<%{8qںE @"7"%9@y9$"W]<53:=WǸ7A g("E>F>}|[Eľ5\)M/Jv\:ic&tG[ag:3X!!蓙;@z` l: MBxI`yv6N4qhud<6!"oTZ ܡ*8h;o_:o͗Q {p~7 5) ø<=0+Q>!8m[j;T\Pdr2x>_եU- -W9 »1ydžAÚ{eQh) X\*/`*U8!5@AjnD-!96VLHwͭ jv%r;{9SN|7ÏO:rxAE"@"K% Gx|\ TXFͯKuuuXMr1١=QB@H2`drv",L=~!Tp4X~)Ap>@ATe3W.Kfϲ$цSC[8ClLI@o~[yoCq& =<4kQb<$OrH۬RA21dQ5eiE>>1{_aHI-?(BS" mt:%DOʵl4HIu. F60 T Ond!$rz %yNi1m ߏ#҄Tjs~o) ]kJNR}$rJԞ=AV=ʨ]P<ЄwA09U(AKewUU5礲shu' (>Sbj`aS"Go**|̢ȴ@}y@ppοRkWSJ ~*HD""PjP WԿ5bkpzMOyFtr,X)s&K8jPbd惢 @rI ^T(ӌ਍FI0M6 @}u _|6LmV": X_S>?8d^Sҡqz4) gPdcӋi6dde(",e#{^&|@(mP)ܢ>; Ryt2"L#9) @YXH  āH"&t)cJ Ak"3mvIXbab @gS7`r'7y<=23}LFa:fآt̫<PqCO+4ԀS6J|@"pfqWqhiHR j oLTmoev VRLL|8bMJhv% k~{?]~#&V{_pp4B>DDi_6; S뗺9Fti,6J0 [l;ȵ3YG(ap7!sps!Hj'+S${Ѡ+|*vLmVjʛc1W 5"\p"2 a3 c@cM^>+͇Hn9{Vzxk^P`+0ȟCd_]80`uH$G0ЗNk])4=Z2EF&)IFog3ʄDIco! Dfv8ڥsߚFh%uLB:-$w͇~-ML6z>+:lT?؅ LJ'Oz#O--pb'HTHLt}|y LH( RBD-!DP{Rd&& $ah(r"H@!g !"cIMW`f vf1xJ濖ԀH2sO*hmMY LvPuH (1 $M4q4rW<>˶Znvf8Zէsߦhey$I)`_BPߝzz8?ߗ&<*JRP`6$<6A ܞWYԀˤCԙE$:R=jMA|!PՠoU/X nt~i(iUONt߽G J;M2 y@aˡvJ\fuCwOg1CXoiDl9O0lNEzD >͵E*}0EPGrh6ҁnA_?gLBIXK,ѲU Vܘ3ЪBp Ot>ZklI}#@_Gq9cI9 G'?WC]V=&b͎RXefFj3.Z HD` 3D$Cl h) +y pd$;1`!Dҭְ,}vD?Aجp>@O ?1(4ɬ+% rg輽[,? C"sꪚ ta"ccO%̥28@&Ftx 1Gy՛fLW /N'̲ uO]}p/QR<迦!?$毜t~Ɓ;G)CQ%H6'=n@\xZĆDzMbgȇJFF1C 4(UDL!΅2)4 % " >5LE0X 5= #!*X'OB٣vMeA$1wR>O˛8=) GLLPEIv|Зvk1Ba` I)gG+H#VfMDfЁ )@cqLJv ;,TYyAιb70o&˼SNՏ=ŋ*@#0#{> 3Ω ]?-XA@@4(HP"1Y5{,vlkE7ᑎ.8^*n.J\|iק!arls*lwrMEN@Di!`2S !u(cc{!i|NcЮ-p "z\)ui!hT@ Tc!@C 2l$A9JFو#|`!JB QC#d$Adb;@3 TW aXK{PǟťDx`~jD7l105|oRbQ]6Xخ ̬\KOH@zM9Rzm8k(ӶR4Z4SCABan Cxt}{r\ ɰ"N{B : *%h2 J >]{@&P4E}>q8ԅ(\3W ;F,RE4I@&8@73M0EkLVWr &63xcRG8jqq`$~l1b,y @~гv~~?)I|;"]Ug*j{¬B|g e4aj3\E%5e%5nwQ#!Sx"`>8e ޷\Go F'CϦkkPX 5rр&G$k0y @0Щ2bUɧNO>ʟwbx$B {=pf@'})L" EB'`D\<ߵE[og:r_KD _'Y)•؈Zu&{0yUr맷ۏOׂۡxvsvtHr|z>-{N'x I 6o˺Zrrss`DnE"Q( O+&1sA,b;Lxd~/7MFJڥ鏧o'至t/مǿV:(76\zw| dW';b6O!km r8Znj|xݐoO(eJq~-]wf>-|q{EǕ#RE^c\*=m#žTN^s6Ν|ڬ_(j5( 5*H-7jn y<C8DW,T)ӻɯqQq;cLƧTŃQthu߆MTE hdJN753;t@NO||"G[`zKI!# ^H'>F͚ˁFjz18=?Bn-L6&T-w5{ ox)%8T_g9g>\[C4T->! 奟M.*H^\e430xQE1#auclC&МC$]DhOҀg9/`&Kl#ĩ~$SNzOe ?%?ZAg n?$ׁ T;^ "!0h?'(v "'~ZI"JQ^F1V\zqʓ#/*Ċ (4&/<ڑ п}3ֱr #FǾJd? &d_pu%x | #,GdžI.A˙Oc)d"#x r}">}K&l(՟[N #߻Ήd='Uh#w>gRlv.Rh9pv^.5!^`-:NFgPDơ=`$#4>i,~0m|THЦVPm|GzEӑHL׎Im\#+&wKO_+ &c4cJaHKVо+`d G,D ݼȌ:A|D"R9 u)P10%2p6-5~lLx)0iVb?s܁G6^ϳ ġd(2~OGf{0 w@Ϊh/.Υ6\7# +:<$25Ej4 IW5&+X6wqm/%̖ Ϭu2x;=q1}bB7q}A@؅(J^n&{99 Ya(qz5١; TqdJIfmٰU֌er(RY`5s [KcxvlG+ J9Xv8 G&"cF+h_n͢2Qႅ Zi i2LUP+^ޤé]=C3r~z>ݨX%vcc͢.b3nud tqǟ r(cC͛o@x9Ba`Je~4h23#S\6z5."0x쇥! Am$x$U0+F0dӗ8ΐ?F/-''ӫ"z>y$vk n tmRML˧O3bk $4]B|/" 0M_݉W&#X,St| aRJBHĺNpBSGORRS'  \C?∃ Π+!Ќ=2? ckUPwcݟ FaDER$ʲC j k"ӡPDGCQχڪ,PUs]800A [hlk'a 1]j*Pх"H a#Ox"H=Y?.PTYdY?0`:ۑ{g!'ks޸i r& vX&c `wjX~H,SX֩R.H$!L}~#?pACx: ބ< 9ki%WK_۰4g JGeR>WRHn=jƕ0BA7\UAoNأLf0IfPeLe %5 69 iX1te"C L28(iEH !NM'r} @&lSOS^o ~J{7BTL`'vT4EBZ\qf&:`S]%ZdBKGSjx;FO!{tQzֶ0ڏ 3IoN;vw# AԆ{I6=)p¼XUɃX8Ɯ֖7a#CiF".:T,؉K^fHI y"T4~L$A냾telF&/1f88.#zZc 0 0|A '1ipXr=48rSF&H4 %h_q(6 *°bgoQcn oH; Q P:܅H5)w:%8* 8rH%5Gp]1<.0pBzW&&Ha٘0.pY0QfNd !"H4zA?tMCP. 2 2a6t!J ]~ϸr/3 H -:䀣|pPƏ-L!)܊| [S rt{=I1 ZGCe=~Ǩ4 NutdCuH]RQ$M:vA0 ִ#BReBeZ`, 256i:ܨkRJZ3)bi)$2BMNJPJʳ @DDL H2A)*2Sv4`XF`9DKP&Ufכe*&@"J6  "Hp*TˆZi= ~((% FģiO>$Ń @ SHc z:֒ J\`m,CzhѷY ;'TuZPee1uå9Hb~y(yKW\-"? 9CIBqX} !|uiU f ?55m||a(Fc"Hv*Ѕ1sԐjZVKz @y](!'DH8@1OͯTz`0(hd$lpM@l!zA  [ ;#j Hm cm;go? E%*s-ETUE 0f|j?nO4=!4RzLJb{\䓿GRԡT3qƂdQm@D0 D2Eđ*G3"D)D3 \ 1@ PLBhȔa aL0Qt+@g%ڝQO<k#++#W{]{NRىP1"5%F@&3tWs\07!q`D)&ruh`2 ֓F0")*3 32"FKB%jhCid#SGpCIaC|89"&11AfCՇ&C͚ˉqHRA=jG\(& WUv~o>Co \ouT n nh˂RL4WugX6xEzhE?[@mh1aTzutNurvc'ˮǽ(iv1`2ң'hc^{I7,;#D(Uؒ4Hfh8/WG5!ݿ>[Ӿz1,vjw؀⧋1 1/!jkW4BYc--Ҋif1ET`bڎ9G^\qƔHa:[8aU!hӋx}^xЗ^%KcfSpfTQ*֘^#5a-xe֕=Nr=t-xЁsv~[t;.Mқi@29E3!jԹCjF!$ 7iZ/\X8fjA3nsV N&4PDNQޥ-Æ9Ekr2GH8ǷlVh+mlP|9QlUIA'kŀFc72ɱ.Bb'n钞Sy$&`ȮG1s& b" XI@75djD W51˩sDJ dq +(, ‡H֊(-FqPiՙT@rsqj.g6 !a JICpDQ3tvQd$T1&!!kPB^VFc 'HtC3 )ÏC8e6H̝N׎|Ӯ_yIܔe L:Aė ᧻\2;#C3f  SB$(PLJ ]eZBf&5i)apκ8uTλL]b #PH  vwjM"sYPUuJa'[iSv Z1Wm`<򹝙Al4%УɊFWUk 8kY[G#-accN{bܸm %h ˷$22yt#֌'V$H#Yey(ZVdCkq6"M!,;  Whj09ݢSBC!78Uʏ%򄘞nYW6v-Ȏ`}=]m!H1D|J:,E=EɂOt)'5f+AdbN}蕯>ؙHPtsqQjC5y V+%6NSjؑtDoPKm,5cixP/&"O% lBVVeBێWwVniwy)L%fZ/~A+Ӟ몈{542:Fu ڜfEۈ(nfmjv[bݪ' J* RI4~4B,.t㱗+ʟ&P D}|kpe,##)(t?y= aZo hؑ~KfHVMΉ Hޓ>_v(`7.A!](zqگpؤ-(/;ۼ!z5GwaFm r0)pPs6!Ye>]CHq̲`/ * ڠV5t fb54WnJ@i X#M)!>(Idp*0 4MHq qt)`],z*ϵP<1x+ǰut7i<굡S!#ggUcTJ `ဒ*ل@kޗ]CY )h7rO IBڼx< Nz51H_ٗC$__ uzGC ۭcd!:5U ȂD)JW7'ޗH*|Be2A i!k-Aq<7 '3-1ј!" B$ӣM$Qic!e=DZ@܍)ӄpB1fA뚦5zvu$;H2"El~[!pMx'(o$هUvSl*5bA.diEZ Ťj.uon>~8Yi.Rб6cBJ@I%4D@PCKF$ 2"T"@wtN9?ىwz+H;O__MI"iDPR H%XBg|ҚI 5Z;æ3q02zXqؖ~-;cr~ SJB11FCOuy;i Y Z5Y) 8 U;XL6e 6t (lrN+u(vc FpBhA!t^o͘@D9~0C񃉱!h{ *~۵wZ JNJf]~@P2帪ajB#F@` bư]zbi>1_q}t?/域i>*,i4 @dBiVanß-N&c2Ph |c Aڽ??P_%Og;o'Xw{?_???ߋOL|?s_?__?ޟ2~OO͟?o?K?K{m_/o?_?s_>Oo?./=ǯ7:/F9y9, o?[ l?/3_ ??ė'OJ!Yk?ƭS?;W~ϟG?O_?}?/N'{˿wo_wuΟ[eS$ٹ ᘹ,]T0dOwW Vxgf[zTKkS2'0@ɣua(cJu¸Ěd,$FHBhJ9d lkb˂;H>l߹6B-*?}T`tA08A9F<wsoipa$ $1o_I :/it*5?ROHT?@#T$Y]CFB'_jљCf0:=u4I([s_>(]YC#?Lejf%{bB^Q1"6(H N^ {r۰;kij\S<|{ue0Nٻ>3`fSnnwE\ k?fz0c԰}S9\z1Qǡəs4q};^&.h~"u;Q@w<y~T#m2#NavEq`To1sgW`0,@g3r\9xqRf1ơ&Bm>ld˾r{~ħƺy'7|GRxbL2t apΩnq,HNʳDIt yWic.&fҔYjL&""{?8lyj J¬ λ > Γ; c> $)O#[8+ <8AZ3'(zB5qFKL -1s<a,sh8ߌ(#~Qyƣc1&&5Kp`qX;N &gFym }԰B!&۳Xu7ZvDV'("ijn*I󷑷;mbmv Lp+"|pϙMLbq uJ%I$0G(PZ6͌<1.k2pŬq,W/cÎ lFvv3IL;=u*MMQrW Zcq|y{QY#ٮtEƧr-^Ek8nv|G{z~ȁ*SS eVɢ>{&;ח\}a+Σkphu4-B-B(}@ s{E0gn'Rbn ;nHli&ΛBئMf6BJK! 8nXr9 K,` A6R\:F`uLjn*E##9 Á:Y(%t %6h4;B>[f]>Ǹmu$s3X\4ZavIF"cq%4q$vܖhmRS*<5˳Ie݀11w!"vq'vM t̋;n"jGyFx4.9"p\;KBg,!3>]r ;rԸ+?AS6)J34.⟺K0˸?֟˜6N-`i@Dô#a0@In s7hRN7DfaVN%*8Pt-0 v ? 6E3 s,ی$Q" D9W؍Ľ|y.XT)bJkٷ^m˓@Kf`4CzLIrE^HH3PIDzcccQ4!Fˌ7V9gvo8 (3R(Dnp6"qC-FTd%Y]2 X2 >$0M@`Stc :|?A4z3>F罣Z" -BVU'3 ^;SNz!履ȷɇv&pI"y?͂sK P% 5ffnL H7e @@b.8,IB0 :-b2Ȕ NQZ 01 ^;"3:4+sEl}4 (bcĪ auuovCqK#r0 8PB9h^ Hc3Tle 1_=w=! 9ߣ/G[E%gh]D R-C5Dw@ JQp gR)I0`epCFݦKepnzt:V\m-mF513(N6,%ҦR2ǂARij1lK!PC(±.49^OE<" dH1B,E"^U+jMA 5祵r'nG ɰI8*qy :MvΩuseъj"! `1W4|]WQ0Ezpks)t*`]g@@Ԟ]'荶9lڃxVY]se#VsPf+nch#0x4㌮W)iOfr|Tu|3$cp"ʑŘe '9}N;2LE,V1xF8E3 bT:ywXh٢:'đ& lfS=[Աz#R됊# Y^NТucf1֧y.9LMi3\!?rFhVVb1$3'Sk(FbD^ u2tPb3+ǔt~;186CIPWv4L_'Ve"QLOq1BD:4B4뎳s.r2G7E]^^'JBB)\kk7f83~=sF#ƺ4ejcƕ5$cHb 0A'G~ޯ5D tF"!] Ĉ#$PH ѴX1 NN=_wjt#QJR M`]Wv2({)Rd'z_\tw'7 n p/!f,$Jvt߄#'Qߞ|| RBHo#ȗ_Mm(uG8X⬁0zu t1 3ӛca" @ SVyXԉ bs^q.00g/}}ϼtz k41-rd$yJ>l#}>] m˚e4 "183%F@8$HvH̝%F8qЖu9 =t楌DZ#1gX5&rG<5@D5'uzްS%)5HL\{Ui aIi,PB [S=Áh,yG`OM([NqLU{8H"O$ux->8ے6—dsC(|P#`i oF8*u\ɨ&e zì QI'w$vz0X)(lOZ~Qx\]poIc˓MhM6fL9+ٶmXsG qsd;:8 'й3*&:(qgCO*:UJ0$,K%(2*54lݭuS`% D?oA ݉;xE8(R @Nk۝6aɛꊚ -~]q)TJA ӝэttH8 1ˌ. tIdkkیWDNZkg a )g\eZ `){;F$m#ZVѫվ3v:VYl1 ZPH"f]L! 0`{qo9% Si(qpC$(*Ho"'|:D uD?攀ڜ%S!0azf9]p!/LB\عYK6#0w[d0֊eC H֪>^lDbE1Iߤ}# ."$`*P(;pi^}qFt/1kGmmG9J}P Q:>~WgLAMH,hPe8Sh rZ @OI]),7,Ï%[mvҬB8 SS;7hcT^<6+r~ۻo)Hw\p6F}\6qdgMp$pxNBpT q{hC/w.U;Κֵ$s:M'#PiVARH`8> ACrӧ=rf[6i>#!^s ̦@;@P-$HB\fxVBNkEBmFg _J9jGDό\Xj59yE-kZ[0ry'Klick7%rFK*ka #y4rN^~z-*g,Uj ԠÔP|U!P @髝htTzB&(rMEwm0rhƑ!d\7Z~Nz2R}rSBGT?JkCKX'DF] {i)ș=&T%1*mu ygV;@`"6CADҪ Q#u$Isni\]6#]:c&5q3ƣ/^уﳷ6*7wa J&:ࡡ '/+gL4L#  Z a\l\jIki/N&Z]s5C>8J&RdV 81íRAf߹5]& cigXk懊4B$wnvQk|X3#N:)켜i!@Ї'Zo6&2a!vb'CM&[+3٪WƅBx˺Mé^uYr0Y,Rc򥷘m˓24m 1\Kl4LLKY[3j OeJsۍG2wZ]BDˑrJH:w;ɳN`hs9A;PxPӨLˉtAW5>G*l4(.AXFf$%]ʯ"iSJ<15KQ\\T6.s\|5]ukYrƜ:pdUA6}\y'(STd`J;S j %#aA_28YFx 4JlGB u.1a' "4\p}Ceő7?l>gPg!/+[:?ųomu'b=Ljg2iWȚR|efD9AЭ/v=Ӏf]3 PpA}ixϥLr{ޚ(4)fH.0*@A Fㆼg(f7g8U]8ѣUe>/Oi|.`jԆ薝Jm2neI|]Q\aR bAE ئvanZ6o;g @D%텫&B(5BW7wM7y Ta/GՀ= #gX  J@ !V _?R(WRCx80«{<Ƌ5h='N~yA? @kqk&p`Ґ mō-̪e&Px9:-3Kyv3ٍʇ(Ï3Ly!4@Yv)hp.=e}yB4#"H5QhT\uxW3b)4=]\rCK"8jXC8,<7 ڛehO_:F% T7ϢDr,!Ǡ93EugAG<zF RϾȍ M%2wWG9FT  x}9g>omJѨgy'ދz͆POa {+t&dGBm`%3 ;1Nwld0b tAEA$8(h [G"SK=*1Zhlp9ͷ-]:.;ǖ Kgg)-* èby_}3=;tP&|yױ} ̆)c«s)J<ǧL@#BAyr_nwLp8·al/Xw=RϠJt}630>s#9P=悙+8D9gkX4F1:ШA/wّ5&DU_nX!E.:N_mX9|2?<ώro@Z Y큶l_:MF2ȣz'.5 yC "$I*Ƒzqr2|BW ʊhNBA<8a *} '_WI߉7t{tw/`sa hIiP6$x/]VxD=29BDuنX)dCk(NH'.9d}noa3j P%atrkf ʺtIZ X&ܼ* zqk9.uoNn2TL%iX|!'0ĝ$&aw*ǻ2 YZN؃8hܥ u!!]q(2P FXe>5s-9!G |0BFA@`LM9/F>[a9NV,ڡXg1(s43,̇#OD imboJ`; H#c )ۣ7.p6|9h0 울d4|zaT*b⮢ql@e|; N⭙g&l (2` adI HF7,kɘ͘- AJI >;zcgi!_-8+<'î!5Z $VzV^"Ll8%HC@R㍼!,.,avg s FjTFo6m:~1Bp@x.agtP|xR :W;zvF7+7;=M/^>WKÕ%RCGubS l̢) c`ħǀKCeJ q5a&# OS"fVٷX!]N7iGlD>!!=jaDA `kV4z؅RTĢ]63jCn6ˍ* 1oa4w-<(}p%i 0_pz1v9ow/ӛWpӑ)$ciJU@uNɡk 1ji(}#c֠g%2)2dDVF4&&}Q9 1Vlf^ "V,AADV q,Zb"# Zv$|'WnLF^jE%61x;=qC2!~}?H*NвGQ~Mc~^7Nѕz)3&&1@jqA" x!a`F85Q^]:|9Jfer'B$( z5Cwdy(wIBo/X􌓺wDL/9:zn B䃼]C}?ȵ!;'zu HzѱEݟ|sևFK,)L 9 ׁAz1$5X`$^kyfw0O1Nm:b_*PȢAs91B5kNFJC)?8VWE=!am 4bPt8SPYFA: `lSB$20I >8a؀q]62:eAA}fdAP5D` 3QNZš,badfe+AIaŭ$FjFh%"]jWY% c#dj0C4"nBaYX6R Փ e,wƴr C4{;~q)*\*,"Ϊm뭰ߚ02FL"ֱf 茅А=-MqɅa/P+tCh~,k)%;:؇Pe+3=Xr8^E!?y>;8BwF\>J4Q"I2jOM@a;%qyI0KZ)22'1[tpaF[`cc0th&w L0[ϴ0ru! Y0IbFc+z5`EA(A,Y[k$)h5~.(as(A=#ױ3 &HHTQSH? &%IEeUJ!B/L @ "-%BD# 4A0Ḥ?;O "^@::o= ~RSAIfe| T*L CB%)H30!;}AyK@[Y;?':Fͪt# > E,^ ` ^0;u0-{!C~ D; T*V*hBBUbE(iJH)ZJF 0J ")( FOd% EU R#)P1i*jfTbZH B @O՝ >>;}1dD&3AVGJAJB2HU(8I SçqH t`daDDُYZ-f]o,Ƙr7&DY$ oN9A7l1u&7 Fcd= wkrbΔAHEN7 ق{<)(>!z`:6 _dq5qö+({b`5DfB@BeE REE$AAԱA52JTPPM%4#q3 DRD:6%R3 5"`hQ֑/NV9R&z5Vh`5 =Q( C 1_ gkw2=aMu@aJȌ't978!ӿ .Q{'Ws''~s=d@v-5jY, ֯b?O!It)fӔyݦpU1BQD(%̪Xx>~gJ\"f~#p2*Icozלr$_61q븤\.6o3';>:EG*O KY, 2sC}T#kJ`zL? 0 ܟd:/Avh۳aV&Z$bo3Y|RIT$vA*y씰W.uh,d1aF'čфxk7Z)Qh}1*x> Oya7RȂkzs^pYh)@qȘ^˔J2"l>V${$ʂM1yAi+U@,J9^OV 鎷Xk'SҌ!-*wbԂnyqh*sP&QG S!@蜐5}Ysu@AZ:cԿ%DC(fdZ+)!/Xks3n0!Tq0!"Zj֌]3ݾwP 4&"3AT@D"PR)B#D`sڂ$kr;JJ5ʉDD"y?1EU`:,H8xe=Bx݀`}Z]DT d Bk\%ee \9Ӥ I '!0b+D] s15ˌ>e69>m(c <13 , óԸLN-,B':BEg"OJ!R"R A2>~fv<CVm6s^EK.tQN N oCQG*V1~xCyPmѦGwj~EKo-2C%T!T7ז@4VA]HR9H ēXt7܀{HüT15U*bgw! -"R􅩮.nv U*zd  ʡ|d~o#5Vfh~X5j2F=2&68uBRD;`& x6|+#"%j7PiU\m[E g.hkL0dihh e(- F+FthbXWMhZJ"l)]E&BC9)E4ARZ+Be#q4VF+ լaFDG,fm*̣bګLvV4TB@$lD!c#cʪnH7 `'19s<㊗OCO=kx{غL IEJR'NCp=-jw~t!eT'{H~NO>$}G.BF1#@Rohr7FTuMKI"93!3<t O-ӑ&$Q oZ Y@,@NX=EUJҙ-Z#Cn()@PRP`ဦq0*DShDO0*?8w]ك UAME?a75 RvSG`uehvpڔP- a:"t8Pѫ ~wruPQTY`M7"]2 cBy)״}!ݧ{T7r!'FYԢ mc7iG_O eeK0r>c1\v&ǂX<bi/bifYVz-V!gURsX8$6 EOiݨJNaIy8c/phm^"1J/D~,#I7C#`s(y0up?Z f )-  RM\tq4F GAaؚ:[?>i6{bL M 3P'UDa{? % HhѱE7+BqPu}Ԇȿ|_ MHqcz5Xg8!W2dӛY8Īf)6`Ax ˓!ϐi>P[ğ#zkb،*BOPc\`\A%Ůu߇'b{ÿy1GJanfpcϗiTCv줥Ҵ=Weg4) |9y3̆vkA7 =HnZXu4L o.?Hcq㨒T?}`3y]ȟ,jo1=+qw)=˷X>LY©<Ӱ0A&iD;8.?ä*'b2Hs#l#,J8~>'c;CB wZz|кTG#,X\726C`1 ba,cT 'G\"Nځ$Cޚ'9A;BBA X;I!^BCR.p2/2h@f1 d^, h<Ӟ\/م㦨2*$D_vy"X-L?#Q9q2zR\+fT?M媔Ch3Y[k4 m@!թd\qg#im*dķ1Ra`FL]$4E/pZrJv$CO\%& PF0A4\wUY;x,:$y 2vc%6Tšs  OT,AiqC7%J((*J֎N#5Vh3%NHX&7 RDlju 'vG]'h*ͳff7!1{ =O*nWJ  !iBa@RA!OQ0DyM6~~{ ADn8y BoڝKQ"~=u\ؾącOd=G]( gu/K՜ZH)1ͶmVZheၱ 8yd&?w2*(("h"d*i) <߲ dR4ʵQ@%^"JXXd,T 0/NJS!Y)) "@"jH&jT% ?T}_>5K=/he٠6m;{x򠵐M@#f&y~H0`CǬoOI 8+$8Y zjt>Ĺ?7$]ٲ v,U1dYw˧Cڟi~BLB݂؀${T?omWJNU{5h[I޽j$_L`xC2}A{]tKPD ϏD3qi{adT8T&ӃjmzX/p258$/U>YHSHۃ:@f!v؏rc5n4 \.b8f@bfbw1t& K.X, z`' vB#Ed$o Al7j7h(XO6%k{ .VYH@0yi>MPuFK[ jJi +\` -aUG&&LtpLDn KBz8BNyPb_ =~{S/al=$]{4[s׮,2*Rh7NQex UCa@EzkKS뼫]*75˼+ H`2AdR5"BkL-Qgc;q3~- Hsh~Sɐ?zH!q "BCxo Uo&3pc?A) GhJH`5D{X2HOHg,O?P_AR A~Mx#9d L8H:D{>>$w=^S$k#8t VTyz} dsKTi( ѓ8dqwkFPd7P`X;pb> لΐd"+ql7߆g\;9&(2W$"p# lpZe ʱZj }X-h¡'5d#{NTPCp8k3c @F0GEa8#ه {@/)R XCd] e1<}#fِΪ(1*I{#3 $uށBֱ/ ZHtTɅ<ٯWњ58|×|Ѱ6: (nEZR?~FBwiBÇ G(#@w[HyDF4 5XS~q g=EF%l0*Q!~m]@6$ q͜ 2 z`F9ĹfBH"]LtֽhٚYD kZum1ut)n448]OGĘdfIn?^EdVb+ Pq3kgו6pp_h$X Cv 0`3 a<1J$:ӨvIWW{8>[w/0ƓSfd*FYq9%1DQRjh.=qWpT`ی&%knL '7M?'bD&L#P_YOqa v,\|} "1;ǚ`a[ʟ8Pj⒙H8QqZz9# >֒SVA;ܾ!#ӂ`K-&=.m4jOA3G1 %mu*[Wtǡodo(eyY ߗ2)ק#IM $ >tqvOcAT)=[/>N-Nrʏ2!_$$ꓤv(2:N\jF(r;eWPJf3â3Xg2r8Q"yԁr@l2lV7J^2[#H;&-+\56ӄ9vb+DgVhhdhv(DFF0\'C:5+9QpvlzoR31DoMQd A$N%yF}0<}F3l}cA˲@p͉9#mdŶӉj'nڂ]iX8cJfbEn cRݦb?4:DBl4wܸsO6\BJd ,Օ'S$E&1 0) ; @PlUO95lzǟU n T RJ ˃ Wmw2F2@:U%QH D2C,{vJ0LTHݣ-FE;P|\ޘ%F TvZ=8C/l @HXQbǰT^lOTY n$-Ex&H{ަIE.{s|\4SL\U-(%: jP|rI/*K9 Nvk`8z. @5R?fNà~gFNd-aeݸhROYT4Џ/Dnr*80 J̕DM"=QZY6Lz 8fTEjp^;99\GYICk ?XQ\ G*FPA H0$aH#gpidB6w*F0xT3$)hB(t@:6*p= j N\T a!d:{6OTCTAB݈RO6`fwX:d6a_|:ȐӍB?x/Ƿ!7M g@Xtӧ[Ţf#m7Fh¦Ѹ tI94S G9A:UCEBbvs_Ou8i'd~EG@#ϩ$]Hd32(W AO{* ?Nty 4N| }u&Q@sԞʌ ]Q!s0yТO'5tQ&Z}Hgd<ܖƲGc;Yzdcjw2#aΡ^u9Ii.pl1Q܀樂a4\ҍ;&%{Yʵ9H8X9d Q2}|wY/M=5ler`];ʎy@ỷ.vmkbCn8 Łgq)KJ0#jQR ܑXy{PZdIL4Cvs)B@AN2mrMn D0(:¼7X bFYiTd[H`xEIKL $`g4/2Yf΍gpoFWG& 0?Km6̮Yfd ~2J7*.pdbLǨwp٣@ZrjOU%?,La?Db \;` آ )cA#8a6B؀0J:5O,@<8m4Xg#JLTJ<9rg,b$l6t]D*әq1HU6!AމqTX3C<.$AtzMIE8: f53~]| pDȸhCKt 4 shIJ!ANCoC4#j= ʁƭ*h:FaDaa_fﰆ2{_:eŽ7 ?}jiҳnt";`~ #1g'b) A,{( v,lJ|2G4M'\@[0?8=ZMߌSlQlsUbL`}ĸO m1Buβq <[U|sͨC茴mج"ӛLvY5h̪c"BhKLgHwUDD|c$|L#bp;J|]8 0 ZbDb%bt7ssù]mɳ F>^}wG"("rā>J# 4yTG<)p6<( Q67 x9 @C5:(ֱ5G抝񧈊YhLXL& E9h-YYX0D0@IAIE NK#^`M\AJ"ä' TUB{^0CxgqsD\NG;Sz)i jg@\/B-vlFfʪ/_w }tӧsjnKs)[p"yB*"xP"7*߉yz0A.ôkTktw0Bph[-$ Ry [뙚7) B|{iҜ BSwCWO#w ~lOQ W:V< Icʷupɭeî6KjIOxǎ4A4x,GTc+Gv7%5i')Chc#: c=Նb)E9"A›M9<}PaP@#Va4\Z/Ǫs) &`h\=1T|d_EK@ڤX!Gt~Z'a^2 DKD$=* P^Ԉ=j R|ACBWSc1k]`\_Fslyi %6Ier7 M0jl:b@;iYRfPY cj"lҺaT ?l +W/u3,ymHoCs›gv{xOl窏DO\ܣV7AއEJ 1L,3F:wz uR-U{@i'zrir䒜zͿ?0Gll)mvqVJm &ʛz ͡(v67K"(<{RZzX8!!֝~3c7O?2 ?YZUO d<@(ǮʂC4DK ;CС8sΕmpG96uȫ`ƫ#J ?Ω~LAhG9 %-YJZRm3+PR6n~ïe )oL!fP9pEnΌOj=:!QR0 |C^g@h N56<+5w\|Q0՛ Cx3Ia7l: ~6`Xԗ@"F 3\5,oull`3PiZ:1Wl(׮^H`jlrM .}fS!hv m12i npdt *ˠ #m9S2\H6?M.HO@F_3Rf^u8p88d 1 ub&3#:fxh5iZQզ0ʼnG!;3CH3 ɑ.=ԃkԆ:0aH푑*1f*9]VZF j :51]3#d22^1`INX7ڤ,)\,:6FьDrXX!9SNHjEYk"m¾NuC92GRfaFj*RI2TeɍL>4X3´rlt_*sb]FeX?4^8Sܒ|O`M\kaB *CwePjAelE٥!!&Î98w#iFV2KߧV-K |ꅃı,JnR D AI`(^ZէL.0bbSlQsaAZQ_́f#)Pȼ _ #M$q1'qߥ[LcMi؎q s$ Bt5GGt#P%.F2'`Cxp)33ҘQ]JFnCPG# x=ֆblzP4# & ^ӧ̭;:ȶiZǰaZ<\ FtQkDHa }`q015E%Dbz@Џ]b[}j5aK ,IC-DHez"AGB% vȾip[|7&>Pփ̜o&Ϊ 3XH D87KaN R%'d &-RዠG1>t _~n_8B)H@O 'lyd$RJ#$%16رzywUݏzA0ȝ֫go*A[&b =i{3&߲R!'?p\86MK*7Y)΄0H,ݡrTY zS&֍6Ɂ3=jbkkS Vt-|wgd3!7]-ȘR!er$ Cex\9Qߙj4r@5aO{{tA}xAgϖ ZMZDGs:zςA٦{iO~$O)#3UxYRo4ktTv J00 s|">3il@=ȈؠenEXZ4IS8US t͓mSVu JݟbiksJŚ6Z5Д z/KPE Z-!54Kc15) ZNacNk2"ydC%j7H 88#bi'5)&@q3TC$Yj W1 4BR3y9hTdi6ȪSY5#BQFFlu e&Gf1CUV1RK-O.Lي9%)Ä ?[xI0L H4(1-N&`0s bMWbmkvhl6."Mʛf;v&{b.Gbm 1tn ̳ Z %X5cG'QDEf_^9}'gZBUZbX(% 0r" Lrp inc3#1`MCQHU4TBMȈ" f GqID!b0eeFA!eU@" ʞx֔!9 z.UC<eXuXUӔ@舚ŴmtW0D 6{Xo.#$\2n>N4?vyl_F礰Rʾ)@Bd ]5>lٸA30(%h,[lsq(n8+Lig_qM_O_-2_3ԇZm%0I@am(3(ݨ L:djZiQŚf MUaObE&U:k0 yCt?1~> c(1<7'@2H"V@P)V7de37U\JT=)` y;wC rt!8@y/QX қ$ Y$/2r2X~Xt*q|6vAvu>P-@xȧ÷ ލs imC 2rd/t>aCBƊEybA9 h@}a3ڦ0c;"?e[C#%M@\à>2q*! 2@6 ڙQǵ҈\>fƛ6 Pۘ⇾p1SBFwY(YhZy/dm0ɥU&J@YĤ~/䦿i@˙9QG\٨իQj+U*˛W˘gVvUGwBLJ1I@?cs3 )"\URDdLF$#]g*ƚNA@H AD T;E"3JІPbQ7S'3  ]H&bcb)k Db h`_ hi * 0 JDQ  @23wdP&n2#$22e@;cM"&ܠ! T4" A iJP"\LFG V5i)Afd b%L(PX` B cBPܦTT Ls1b@ʧ,5Xr@fL EQdԥCIy1%Q;Tc J; "p!pYraaFB` cXQ*6 Бl\cHIP(Z DB"$ Cd$ۥL9ٰ4ѱJY.fJHFU J ghFea`RFI!8! rr?T*jPEdܜ*6~` E](e A9(sy! g| W\^R4q ,I *f+赥hWM$ (ծDsu=0:'=%CFfgPʯm%s8^Y_ wI8zd@ȘsL+Cy`4@U!Z|z|QLE|P9U!YJb$v>dt="i)dv,@~y8T(KGoYe\[Xg[dHbq[q%sI>tu]@)B;w~﫮}i*-0c$:h^k:;8rZ;BiVD nzdî_TСGw6)Z}eeXṐN{j/p :`Rn_sqА`B:F@bCxGY/.܄9hAnwʗ f}^~jS?EiY3!ff_%!餴M?93g&TClܨ Mf8,{m 1L60ȱnxs5'g_UMl3m@@թywϡcJd> $,$r,#,cNK^2fY83bחijkӃr"T8^E 3'(CN }S0A7wI- & p_lO!޼l^U@ WNB!cT;]_3F1DPMM4PA1EEREGD'L]t9Wq IдsbHۭ߄ڨġwy[Qp0;Jfgq݈S ȁq]đ!+M!5*%.H_Sނ@g1IpY"}^D$]l)ș$ͯz@hP=&'@`cSM[LVH_?`}RkTf &6t-j {t{y Rh`2VD _J;|'qU P(d $ 3CZ dU@DH")W @iCR|N^s8rUEP0r(:وSmxgitc0"ub% l!b5:LHPR5n"H j "K(l77&J+cx) "P"wWÜM>H1 AU; tAhC!̃V F}tB CyTqQXr n( ^% `BpcL U E<m| 0اp!H4@C F B:$M)2H;>%`? i{[E~A~W=DyKm8{%9 L#k*%vѰ Ej fDEBlǞ.l^EMMB?@i᪙5M6Ɓ(ǥt=&OY/a~]_{l㞒DS! _6&r4Ŧ4B4#2`A.`#pl|*"557hm Lni R3&9qPNir˿! ""Jă!MMDEDJQTȘe)@7%xCF0xM'P-BGLz9"J (R*)d9fau ** (? I e@ D`:B*6+"jB[D3EӚ$O(Mɘ'1"F)!<|` }z撞鈛"u߂>)UPJu$SJRr/ .̆ [:q2kD 4N)x3*5w {xIEM{a|r^tc;;7X!)\T'T ވWD0LDBT!(@aAz!Q*PdՒ QL!F97%:'!Ob`Hn' =#jx(}npRLfC)tU-H(uGyᴟMX. Y<)@1H#J=5`B0aS0WŨ~솴d -hhd/ kJe(S:i^݁9DTAZ)TL}!Z hP&CddH>*rB#AqX{]j~gUW֑,vqq >PPy*E4\q@8U!\HOLKzMB *fnüV$z Ѥ/.GyxM24e%:zl>Z>4 g`EQB)|:~_ i2͒`Z,41eo|N(  *wI<Щok.W$`Cp8}~BueA@HB+DC8C{D\hS9 3 HLB47cV\X4F( oNu!F x;G&:hl}=0)8N!mP@Ӏ,  L8hK)΃M6V;Uz3޽I`$@^}ТLIab#uZzJioj!l~KdLF w!w_EP!!# `dIWL:SIH A#F,~YF=MߨyE1:*XpGoց#è_gৼ0!E% J^OAKR I \OyP d_|)5Aq8P@*h@|0$ a- &00 :-\Z (15N4s c\xZHo$Ċl2ׯk(ǂS|U0]_LJ^>o&*"blԮij:Km =㈙PqX1(XAikc(4̒J*.Mk+c0AIڤwf&BA ֭IքRr$A=eCp:}2g oD FB f߿e!HS16u-9ݲJ@X[ ˆh$e ΢*@5oJZԎoEpd0CK݆ M | >X@y mpijM{ $2 Fu!$zvM{"yR%\:N;}y۸ j&ifR"DOT !yyfIS9debm4<"r"32ID`ybWY si LI "RRG1aG(F>+2R֮6mE,St5_>k'2>Xes,5b]d.~8w~نq%Ô 9pϊcaKu7G\tcs1RC}tLWLAq0Zh0(+m`0cmRREd,DF\s-&:G8$1QIR|OX"ʊ|i< Ycj-i(Eq!""n(FD1YjTNnʑf4c^ߔ 5`G j =U4z:>0ҴlIe=vþD9_fm~?pgbcnxYBq8-Kg\p.;  ܎]) c85\kޮ's,%mlYF~(]u&'U'MΟM#˅mrA2hv&j2z~Ӛt7މֵ"Vֳ-urkpq^MGLޡZ)Xo%OMlG@tA1MUI!H%r(ЛZ \84(D#)碊xTt808*8ZAbY6KaA8$,?Vd! 2,UbD 1j%A$j`D=>ErT|ՙJlPjwa̔&S!l śq}q,terzxlg<}Fmo/0v5?"͞ʃ9~6>k͟N_ިYvΡs30(aU1Q$$ Ź{ k9qpH}=|=^z\M4,;طdR7pH=|`[$|1`. T!3VQQD6-zLO/ǏJ5F'}织v81=DL56#\pYsCYlރ3ϼ媢b q+TPB#&qa8YF S`?J5"mӱ¾C+1aA8r~簬|>CRiPMX!sC>V<~gm6]d^Їj՘h}t/Bڗ/rn2Չ=wrYEјTYeF 1TTt*t=Cu%,&|4zo#Y4ߪ%(䀛1Ml2Yaa8`24@[`ҡ`cMep `[ Zk Jh[ 'VTbDA$o``&3 ,ib5Lc 6K${"U1 4HŅPK3S C9J&^|L`K^]3p,EMkaiHh#KF&0Ad&A=Fi0hf1N R6H' E-Cצyn-hNg4T @`#@V9CkJ7 lD^6Zѩ3 xp&i*:|m.ѨT,O B(j,ffLc- Q l}j5X~'P8bp~vBmyCQV k-9`)ٷ3|vf\逞S JCsC^6B&p1j)rN5 M+wJ1WmM-# ;瀁ڐP MiK3t8L 4!|J/Ƈ$N#5M?OR8cFӭTQƯQZsIbc7ũxçp)8C86ck/ 3 m/1•KM{zI|Mߗ"zE[j ,aBhPze[$olN罰*9S,31 M֍kn0 4О.} 0s`!F=S7@)SV`0HL@:XcC@ݹӍ̬%5(Jؠ.WJ,&TR4pྴ .f#tt,!2}|1ߩF:+*ZbYb~1r&X9Ͱ9u|>y~+ ů2DS_zbWa^ȍd&ԆI+S6%=<Ї~Po@&HDH :8%ZPpn0-}D<-'dB@ CT J'frz C9O:;qԍ~><]jJ혨gՊP@T d<R"́bn[m(Y)>܂I=bLv"/\z}ThH~Q&எbd*[}Q?7X!HG׹R1牁ly\o3 Sm m~GdOoh^޷ "1Pi4a `E(o΢b/;>gb>  hf()Ah4l!>88/v⫾bC'vbzI)p}33NYSKSL’ !y%LCp7e˼)h Wi `$ۏ:=yE`9OAz Pz|UW@a4<`F/>Ѯu95Ƥ;3akQrR)R_&|. P݉!Q MHl :4k) r 0J(01.y k)a!U(ЈN0:Xe!gałE&--;/ iM>q羢s,&\@dbCC۸ݻFՂaBfby`k=9F2>#cO]vb̙δ@΍ʇAIb٢ʄDQfBʺ @kc(tCL4DHPjApcYIO1oIEήiMkR-Qj%Y݀ &&&MbX7&]Մ'$ kUk54Hzdb.B.Þ-V)cYOLdm{g̞*n nݟ8GAV$a@%P u5 =:u]DXN iU\2e 9/ m^ٙ ` P"*?t  \~\5uAзwn+F\@L1` uJ/D%@(B{O2;KS%!iF  P$tyYN|:u wwJ'Ph~n}Gh=BJa CC@JB݀9=X# "D7 πQt5S֕yy8wc٬6i,SZ%AkG? 0Ih#Pζ ̪Aɏ[h@h, V+<7(VHA"`JX% `i'JX V(p%a@&rp!DCVH!0`CDPk0G#@ k=8@>ܯODDJ*^1a!"B $fHb DiFW^׬Ȩ@݌hH)XH)$( h= 1[@pGA5O*5Q(?lOtK@mA@K?z)aSA%*&@5G鏅kп$Z #޷eq}0%S9 k@}g> }N[4n#YUdC0ꄍgNͫB&/s^|WգakI)mTiڕVԩm!RBHO [pi1 dQ5-D5aʣMYTR<1baTD1 SEN]]v$vk.fgoڡa g'4,91uvU9}H;gmA+r?=_<@FJ*1 Wp)a|m6$kĚ;ChLzy#T(ؘibXHa xo@?{xċPبdcXr*J2O.L)IXD!\ʪthP 1Y:S21[P!RrRbD]!E<68,]C\iFt<}[dܛ @Xq<xsol#,}|V}G4FL3 |TKp|$4ue*u /TH:FQ3䃡ώju:XC0;N!^7hN#KAyOg4*y/QiU';xc2ڗ5`Y3' AP$i /J&,y4xlfVnLZV=Jf3ڜXNb&{.PV1{wSzqnYk\ bx{PA|qys{31$t&f L!R[7X5-Ъh*bU.Dc1߄7ق.RqN嚖0Bl!S+N3qXrfv".!fQ}ӯ73>a a]ttR5(LL$3++/F'Z'͸0$!]Vw2m, **wwwa}\w;{{Ѝ8#˞I;fߎ,ciR;12VMcqD%%ZwN}1n}b^L0 X>vM+tk0g݇}y͓!}ĬsRL[, Twe,udSr ؒu.u3,=o/'q `)"o=W!{&fn-<{CG,h;:=Qd&|՛9ǓIW9!nLTN:5^Y0:3a(㬎ÝOaG y? p,nz-pҷ/ 2W=?X9@p#k "uƗ4 Gu Z}a,yq'0sss.Ǫ48:07ޔ3V&o`5 -Ŋ9}8vfkMHaݳi n n_} r`9*#8;c {Y0KʅBNbQv2=~^ќGԷ/rMm肺M|䟳L VDɓt.=W潪[R"0Ob'fqpqDgtklV,qþyU/S hW ڜTN /'KmG]fOڼ]7FǾ/*I5{uf /":zt&T^Zi.`J,|bR%N DI L#!B/ZMi&vQΞTS&u B- H)Lx/C" %"#Y-4*dnPj)"'RPIBn ~X >`NoWaRheK_Pv !Lz2(z฀+vNο0~;"րBAتP \$:MOyT] H7U'•kEQ~.Ga3>%m[mYĞ\Wc2dDo'_"2G~f0 fm߯AR&4+b_a_-Yr#amzV\GKϙD8nl+!ڜ#`cuRSEBlD>m!>8K&`Fn:m_,qA˳ e1AH?= c  _4M:r iiFZ#&i[dUL:a7w:Ҷ /qx JByFY?%Q?̭4s2̈2QyD_+zmB)vzf!cCP(BHrYv͸!bB9\M\Y5" 7ag9*L!̽QoDI3cd9 YS8\mWF1s5ؐ\=loz[#3mV@A@1!iM@YAJyuΔ9ӥ&G$$m Q^liqzfFd2%@ı j5#4b[$1Bz=]wTUT03Sk=%=6S] S 7tcu꫈$nc⃨պ7RpDIjx3Qž0L(Zۥ>,5Ԟ2ǝ~dPfaTDU)T% F c C"tadB"ߨ{N+J6]9r {~X5Q46W ##KGM=_Nwp#q zL  Ol8o!@t;EtD `4*6 ,)NL03p/E2F ma8kB1B Ch! p6Cqt#0M(LX^ۥ._#^oyzR׼-- 1U[g#ogJh=5fֶws?Cj7^< >yz햀BdH}7YC7pDzm{ʶ@Cܛ :pd#T.mN͐Uħ1(1 T %Dtb!'ugڜ! G"^ZӠ!!n4{ j0|SDQDMM$ S~;j +QߘV ^RAc]فDJE`k R\0ƅz!ަv.JRgXM0پ߯ɐ<(0]'T H:'ˑ?Gd~ k? ;R=.ܻ;Xp|+GpY^t2=ۮ9a֠ zrܭc 9{`DLLU4u۪!Ր-3 TZ YdH4Ꮯgw@ȶY:ȸ񇨮,^^Mi<0C82x0W>h/|N`_(XzaWRtXG5BI jwE{S8c}A-: d>+S].d,EcQcT/yBHx12tBsC'J5 Toz)Or-?mLp9` IhpJ :lRmʖ%Pv@pUq~4F0ByF KNOQ4lxƜ X@<>ts)+G+wVFa>c,e愦b!RuA׃< 5°1+Cٿmq~sFiw @,y}tHW>e.SK7obA9¨"Ĺzf;@&9L%$BJUs+8T >c?&.qGf) @c+#؎6al*jj5RAsƚ=9誈jiUO5UE3TN+DՎJg %H0&u3)"(/&seNe@^ iǮ3(:C7vq{FRaHtbI8+ay,&&(OMw- 5BގW T0'%BenL&GBe|jj7-E]ba ;9'6/p4 }gI#;a[y M(D,aLlxG33Ѓ<6]-È4P@0\_h# S+Ac:3퍂'6AT [Y}|? 2Gsc I9 j7km.vϥ܂sW2}eGJ iuEBaWHPEI>cy۾dĪ ]kI*I*,nTAˤLnD &F<>L:" M2ɋD:ێ" $R0Qv-ja4I&&c'q CkQiA3LUM5M MZ^1.J<%B(U| 1!8t$ü) O^;نOE~ D8=G$=&ځ5o=Un|$/Y H*oCO 1 ZxwV0"7<G>!@bIm.׎`\V*rv`fK">.̙\ӃBBH/$wTHD]U7[_*5f03hݤ4BF؜$bL&"& ">gæmhr R "䟥(1v#Wa&1RiԣuHxQd k-a#u`5cIESl6,LTufRiFb27+FQTX5Ul!MDzFVTjAcg Ĭdƫ5SzHs[fUSKE t;FM6\v "kD!%CUoH `p@< -Z~@$"J(x TAL'ayw;X8+z/^h/}`,ұ4PDPQjĉF$D̈]0hj pL+E} )݆|[AP3Q2F0)f:~p$wsE}lbuw'6=ݪ/gnTr(]M?HPh\+_SUv,'YBsMI) 9o) ܇nQ5?Tt'xi5VmX# -/#?cX穕i1M݁NPC;dLZnɯ8ӵ醿Mh]ٍ>N)j ew$b{4OZh<2h H٭ArW.xx yf}899[ir5 8D(.,쵽fu×hs;M\E|!rEGS:¬tYnЙ %qIk2 8f aĔNt uئn%4vh@48)C3AEFcĐ2`A֢oLj(C:31ĶS0$&Q\G&Ծc{ԨN^0\rgVTEPgԈJ5E3G{vYmsiu`d1kua!NA#\Ý;9ñV Gj#vmyGinudM2æBg; p12d x˷_)K3}Ζޠ|v.HIA5qdCթv388mZ4"ҭtGx ؓ{˺|< 6O n3vuq;x5ü<r OKWbĩ6wfڍdA "#K 8Z2LAt@un5&2)FJ`ĨW=z`ovM-_=x6k7idKF$ГVϯ8wt4+5p!zWǁA-+`n.(4 UKfpSKPsM=il^,{lٔ;4)zt*XX782;d,lv;K(لFsF w›Y"VoOEbG N?R\ M^hq}L'l/VRMg G}O{ro.M<񋉽Dc& [bi@$S,Xgދhaw|N(q%2}U'dCa"AIP g:oy;NATtΏ>uX;-#5K ?t)]qE&Fmb8ȺP$e7F4:@(m[`1^^91=dq:Nue K$$z1y-ӎ%7.k,t.gfZsSr;Kۄxw]֛x?5PR܎G80CFMضh,93:օC ڰKCiq\zoOZr=6Zp1l L؊EkGA&&/;=y? 5a/~PlLmlRQ|7gи3Q~<(N 1y2o؃2xN#nz^icw4QL0I,iUgMY1J]\+5XtGܝ}:}\G5؍>,Xp00 S;"%5aD܈kxBuu[1l Cls { S<2HasPxsdԫ0~(Jf5 ,CHBBfɋJ4fi '63"#Kr#4*AG#D"*a Q i : 5E1wTA4m,@n]T(\6m!`}F4Z5.4S&c@#CyZMQA4,aΞU 4h℣7<wE>yxҴ>ߛmxRBEM l䥔@KmW-D#UBw?c.OPx]r2aSǰ:_ v),1ż`CJm2c h%qRL+BƘ_7Ê\%Ql(_@k< (8iph(&r H 6bsd%!SV" CzʯD~!XU>H4 Sͷ^~lהxzgcYs*H nTQmCYPTJ.gG=(qQg9M)҄HR8=/ԵK$Z7 I13ϳ 9O)JA;-G16d;x@Gk K&PnZ2,9"Hv&SE-;EEr4W n*VSSK9:1`'&U11HB"xx4Y>eC(爠H"d 0c4 K DFz5BcYsr\aO_"R8kX AlLFՊ><]VdkYN=\P<5{8#ቂB'ah'|ꈫA!]8>+ሟ(xO?jh@s5zꤪԩuN~v>KFQnP6F`@1Q#]#XCWZC-Qw "oX;,8 qti˶p&ITyN "tMjᛨ|RW-QTyꓦŊA:"'l0>8"$%6߄CWPs;!A,j:JJlHaAg wr6OۯPn)6|VSk 1 n~*|I J~=za%_ cDOeoQ`~%Ē,8ڥNw(rYZ]Nz5`Ÿ6>Ko! "hPiG.^ aN@=W9TUmjl m;3h(vJu gF;;^.y))*`Вـ;xvpCHcLcQƥ|0ƙsfRFXcEa= G5p,*,Ʋ&+X;Jc1tUw2#1A,@,Hl1IY*&Ebm 1̚da9VBP&\Im2>)X jN13Chm Fҏae eCYZ[ur-,JFu»-tLš޴Č%pޠF]Lcd#xӫƣikJCȉrd֢s:Btd!dҙUATIS\zhDĵ]@ާ5C Ae(u}eH˄EjJ6DH\n^AT}"x?{ ؒ`q=fiQ^BQ63DZ %M/P^yNt{#?dpcaxkQG݌U4{G8gH̎eJ]l~2h /;15:wgsP@iǽ>kh:S|#KHQLK*RPC%_/@Q 8+ JP:BMɠAсMH,E+Hs a&AĽ:8ɤ됒 Xe!! 18Ⱦ]ߌǃ҆PoB"ԅ| $ztMM@N:-R,YWHB~7r$B_`VKnd!P|pk#4_e=L"gznqG1^`5h3KN"\l:гi!s׹5#\̝z)E?6ꌪOM1$ˤSwF0\4ĺzp`<_ vqdPgwq+WVhtKLAMN..![0Lem&#mf0GV PB[4 }NK!tJ(FT+_&)@}08Ν] A1а>cici guٙl4 #q826>DnѨv|9 h@n1K@>UFSY`a6p!K$ s0Sg{d8v~f{!&% npxjD3j'=.#E>x$.%fY/vkؘ}?ili)("['Fȟ yΧB7^֡#K߳VDǴ`y=vҨi"/6|ښ5 1Op\Vu]鬍/:qH{>&-?vpv(b&t!"7j0hl Յ)䌝bht@ y^%pkK{NP̀I}>=k$wBǏۙ a@AU/8*o+:PA?GP'`$PI BUYHWTU `zO5+"/I>)eO#"wǶ~0s yHSHPu\ #2ȍ|zdsD I4IdU+F.& J#f)zdԹpOr(% ɃCЀs>$y%>D-~|:`Le 12I;/p:qɒ?Mg|-oAA!>݇.uHmic? ! .u G1|-wʔ`U?HM@{xGMx|2ۡS`s]/7Jд y~6nEM8bbx lhnKɌ! gXăi3a9A2~rN:SosЕo՛C9!hl=+#{hq2cߍ8qKy_25{K864_: gn1e#dZac#9%8vX!3 qOdRyR\Xk13y" 2ƷC"q%'CJ<E_?sn>!pG[xU>1%R('Dr_SRv!0>{D`HK1 wۏxxBޜaN2Ƹ2K@i|0! 3)@ko \0a$)Pÿ GE17HIaV#`6=Q=>.„M>Y!~e8M 9x ;4~f A* iT@xS;8EVf A<&DF*1pi !H0qRFJSьbNa-"yq H@apK#\-"Z|7=uۍ*BRҾYH1@HU#,BeFz؀O8/`w T2*'a}"6W]18<,6@\z`b-6*f@ PCG_qہY6iՄ *Ψ=?~ϝd+xpGÚr`<;QE{8C04CGY\>޸ߣf:vfóˤtDtjc6AehN/kvhX=YrgqT$0S=Z$-è.=J<$M#A0,t,%bB0DAx"}rwhh鏉} +f4Emb>S)AxOUT D9<0cBWr&F`eh _[dT.Hv! Bfm.W]"&>^LAF}yxǬ#TѶ+~~{0ԅ# d@aMU %$QT҄$TMM+/xB4=W]6/k::DjIwǬa(c7CD$)uRE!Wܙ%!lٚ,3 * ,NeD` cJ&4'\¢(h ,q23"YdUujbjE pLA#JWi1[Hi! xS`rq8nuh;H':jDI%2%_)e/A>rB|U2Xj(^tg*2@ UPT4os8T/>Xcj6*0~~6֬`F^{= =}<%pUT1)#&уlѫR1L,ɗt0KD9C(f$?Vz^odPTOt]u("6f&~z*fjs0CdNLԔ$@dr9sZzP*:A`qO&G1Jh" 9 |TQC>6?S]8`ƯllJvo |*HzŁ&H 2!{]ZFBJd H )0E௧ OF珞|UmA@ A(|; }и \Y?mŧnAklD.ng[$[๿ bŒqMPN@?S߽E`P;"diI%3pW0}_9J( 1.Rz8]愙ReTHl*Fh8&!9hPLj[8}R٦Uzdʬ>:قA[8fvf'6qn QwQX>K>qȸ!ٚ$N0'Wof ly|LVVw)0S(Rf1úҡ<*|j/q4=M 6A@݈}IsƄћ鈵:aDoȻD(,Ըqz[YI\v1$m77rf[-Ώm6F[tx')TnM&gbcB >u,˧^Djc#$l