API-Plesk

 view release on metacpan or  search on metacpan

Build.PL

#!/usr/bin/perl

use strict;
use warnings;

use Module::Build;

my $build = Module::Build->new(
    module_name => 'API::Plesk',
    license     => 'perl',
    dist_author => 'Ivan Sokolov <ivsokolov@cpan.org>',
    requires    => {
        'Module::Build'   => 0,
        'Data::Dumper'    => 0,
        'LWP::UserAgent'  => 0,
        'HTTP::Request'   => 0,
        'Carp'            => 0,
        'XML::Fast'       => 0,
        'version'         => 0,
    },
    build_requires => {
        'Test::More'       => 0,
        'Test::LongString' => 0,
        'URI'              => 0,
    },
    create_makefile_pl => 'traditional',
);

$build->create_build_script;

Changes

Revision history for Perl module API::Plesk.

2.03 2013-08-19

    - removed Try::Tiny
    - Changes was formated according to CPAN::Changes::Spec
    - fixed tests

2.02 2013-07-10

    - Added DNS zone SOA get (Jari Turkia)
    - Added secret key -based authentication (by Jari Turkia)
    - Fixed warning about uninitialized value
    - subdomain added (Zavarykin Eugeny)
    - is_connection_error modified (Zavarykin Eugeny)
    - fixed warning and bug (Eugen Konkov)
    - .gitignore (Akzhan Abdulin)
    - fixed bug (Ivan Shamal)

2.01 2012-06-25
    
    - Fixed tests

2.00 2012-06-25

    - Improved is_connection_error function
    - Added user component (bgmilne)
    - Added mock module for testing without sending requests to real api
    - Fixed bug in service_plan->get method

2.00_3 2011-08-01

    - Added site-alias component
    - Fixed add and set webspace
    - Added tests
    - Fixed site component
    - Added sitebuilder component
    - Added ftp-user component
    - Fixed hosting ftp_password bug
    - Added dns component 
    - Added mail component
    - Fixed is_connection_error function
    - Added set and del opetations to service-plan
    - Added test of service-plan component
    - Reformated Changes
    - Fixed Pod

2.00_2 2011-05-16
    
    - Fixed xml renderer

2.00_1 20 Apr 2011 10:52:43

    - Fully rewrited
    - Compatibility with Plesk API 1.6.3.1

1.09 2009-07-28

    - Add Mail/DB/DBUsers/Domains export from Plesk

1.08 2009-07-23

    - Patch from Nikolay Shulyakovskiy

1.07 2008-07-31

    - Fixed Build.PL (Test::LongString not in prereqs_build  list but required )
    - Add URI.pm as required module for test stage

1.06 2008-07-13
    
    - More improvements in tests
    - Get user domains list by login or client id

1.05 2008-06-15

    - Added full support operations with databases and db users
    - Fixed errors in pod documentation

1.04 2008-05-29

    - Added the ability to fine tuning the abstract_parser sub (for API::PleskExpand)

1.03 2008-05-25
    
    - Small bug fix

1.02 2008-03-22

    - Fixed errors in pod documentation
    - Fixed bug with keys sorting in Methods.pm (thanks to CPAN testers)

1.01 2008-03-20

    - Fixed errors in pod documentation
    - Fixed bug in Tariff Plan Change sub (Complex.pm) 

1.00 2008-03-18

    - The first public release
    

MANIFEST

Build.PL
Changes
lib/API/Plesk.pm
lib/API/Plesk/Mock.pm
lib/API/Plesk/Component.pm
lib/API/Plesk/Customer.pm
lib/API/Plesk/Database.pm
lib/API/Plesk/Response.pm
lib/API/Plesk/ServicePlan.pm
lib/API/Plesk/ServicePlanAddon.pm
lib/API/Plesk/Site.pm
lib/API/Plesk/SiteAlias.pm
lib/API/Plesk/SiteBuilder.pm
lib/API/Plesk/FTPUser.pm
lib/API/Plesk/Webspace.pm
lib/API/Plesk/DNS.pm
lib/API/Plesk/Mail.pm
lib/API/Plesk/WebUser.pm
lib/API/Plesk/User.pm
Makefile.PL
MANIFEST			This list of files
META.yml
README
t/compoment.t
t/customer.t
t/database.t
t/plesk.t
t/mock.t
t/response.t
t/site.t
t/sitebuilder.t
t/site-alias.t
t/ftp_user.t
t/webuser.t
t/webspace.t
t/dns.t
t/mail.t 
t/service-plan.t
t/user.t
t/TestData.pm
META.json

META.json

{
   "abstract" : "OO interface to the Plesk XML API (http://www.parallels.com/en/products/plesk/).",
   "author" : [
      "Ivan Sokolov <ivsokolov@cpan.org>"
   ],
   "dynamic_config" : 1,
   "generated_by" : "Module::Build version 0.4003, CPAN::Meta::Converter version 2.120921",
   "license" : [
      "perl_5"
   ],
   "meta-spec" : {
      "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
      "version" : "2"
   },
   "name" : "API-Plesk",
   "prereqs" : {
      "build" : {
         "requires" : {
            "Test::LongString" : "0",
            "Test::More" : "0",
            "URI" : "0"
         }
      },
      "configure" : {
         "requires" : {
            "Module::Build" : "0.40"
         }
      },
      "runtime" : {
         "requires" : {
            "Carp" : "0",
            "Data::Dumper" : "0",
            "HTTP::Request" : "0",
            "LWP::UserAgent" : "0",
            "Module::Build" : "0",
            "XML::Fast" : "0",
            "version" : "0"
         }
      }
   },
   "provides" : {
      "API::Plesk" : {
         "file" : "lib/API/Plesk.pm",
         "version" : "2.03"
      },
      "API::Plesk::Component" : {
         "file" : "lib/API/Plesk/Component.pm",
         "version" : 0
      },
      "API::Plesk::Customer" : {
         "file" : "lib/API/Plesk/Customer.pm",
         "version" : 0
      },
      "API::Plesk::DNS" : {
         "file" : "lib/API/Plesk/DNS.pm",
         "version" : 0
      },
      "API::Plesk::Database" : {
         "file" : "lib/API/Plesk/Database.pm",
         "version" : 0
      },
      "API::Plesk::FTPUser" : {
         "file" : "lib/API/Plesk/FTPUser.pm",
         "version" : 0
      },
      "API::Plesk::Mail" : {
         "file" : "lib/API/Plesk/Mail.pm",
         "version" : 0
      },
      "API::Plesk::Mock" : {
         "file" : "lib/API/Plesk/Mock.pm",
         "version" : 0
      },
      "API::Plesk::Response" : {
         "file" : "lib/API/Plesk/Response.pm",
         "version" : 0
      },
      "API::Plesk::ServicePlan" : {
         "file" : "lib/API/Plesk/ServicePlan.pm",
         "version" : 0
      },
      "API::Plesk::ServicePlanAddon" : {
         "file" : "lib/API/Plesk/ServicePlanAddon.pm",
         "version" : 0
      },
      "API::Plesk::Site" : {
         "file" : "lib/API/Plesk/Site.pm",
         "version" : 0
      },
      "API::Plesk::SiteAlias" : {
         "file" : "lib/API/Plesk/SiteAlias.pm",
         "version" : 0
      },
      "API::Plesk::SiteBuilder" : {
         "file" : "lib/API/Plesk/SiteBuilder.pm",
         "version" : 0
      },
      "API::Plesk::User" : {
         "file" : "lib/API/Plesk/User.pm",
         "version" : 0
      },
      "API::Plesk::WebUser" : {
         "file" : "lib/API/Plesk/WebUser.pm",
         "version" : 0
      },
      "API::Plesk::Webspace" : {
         "file" : "lib/API/Plesk/Webspace.pm",
         "version" : 0
      }
   },
   "release_status" : "stable",
   "resources" : {
      "license" : [
         "http://dev.perl.org/licenses/"
      ]
   },
   "version" : "2.03"
}

META.yml

---
abstract: 'OO interface to the Plesk XML API (http://www.parallels.com/en/products/plesk/).'
author:
  - 'Ivan Sokolov <ivsokolov@cpan.org>'
build_requires:
  Test::LongString: 0
  Test::More: 0
  URI: 0
configure_requires:
  Module::Build: 0.40
dynamic_config: 1
generated_by: 'Module::Build version 0.4003, CPAN::Meta::Converter version 2.120921'
license: perl
meta-spec:
  url: http://module-build.sourceforge.net/META-spec-v1.4.html
  version: 1.4
name: API-Plesk
provides:
  API::Plesk:
    file: lib/API/Plesk.pm
    version: 2.03
  API::Plesk::Component:
    file: lib/API/Plesk/Component.pm
    version: 0
  API::Plesk::Customer:
    file: lib/API/Plesk/Customer.pm
    version: 0
  API::Plesk::DNS:
    file: lib/API/Plesk/DNS.pm
    version: 0
  API::Plesk::Database:
    file: lib/API/Plesk/Database.pm
    version: 0
  API::Plesk::FTPUser:
    file: lib/API/Plesk/FTPUser.pm
    version: 0
  API::Plesk::Mail:
    file: lib/API/Plesk/Mail.pm
    version: 0
  API::Plesk::Mock:
    file: lib/API/Plesk/Mock.pm
    version: 0
  API::Plesk::Response:
    file: lib/API/Plesk/Response.pm
    version: 0
  API::Plesk::ServicePlan:
    file: lib/API/Plesk/ServicePlan.pm
    version: 0
  API::Plesk::ServicePlanAddon:
    file: lib/API/Plesk/ServicePlanAddon.pm
    version: 0
  API::Plesk::Site:
    file: lib/API/Plesk/Site.pm
    version: 0
  API::Plesk::SiteAlias:
    file: lib/API/Plesk/SiteAlias.pm
    version: 0
  API::Plesk::SiteBuilder:
    file: lib/API/Plesk/SiteBuilder.pm
    version: 0
  API::Plesk::User:
    file: lib/API/Plesk/User.pm
    version: 0
  API::Plesk::WebUser:
    file: lib/API/Plesk/WebUser.pm
    version: 0
  API::Plesk::Webspace:
    file: lib/API/Plesk/Webspace.pm
    version: 0
requires:
  Carp: 0
  Data::Dumper: 0
  HTTP::Request: 0
  LWP::UserAgent: 0
  Module::Build: 0
  XML::Fast: 0
  version: 0
resources:
  license: http://dev.perl.org/licenses/
version: 2.03

Makefile.PL

# Note: this file was auto-generated by Module::Build::Compat version 0.4003
use ExtUtils::MakeMaker;
WriteMakefile
(
  'NAME' => 'API::Plesk',
  'VERSION_FROM' => 'lib/API/Plesk.pm',
  'PREREQ_PM' => {
                   'Carp' => 0,
                   'Data::Dumper' => 0,
                   'HTTP::Request' => 0,
                   'LWP::UserAgent' => 0,
                   'Module::Build' => 0,
                   'Test::LongString' => 0,
                   'Test::More' => 0,
                   'URI' => 0,
                   'XML::Fast' => 0,
                   'version' => 0
                 },
  'INSTALLDIRS' => 'site',
  'EXE_FILES' => [],
  'PL_FILES' => {}
)
;

README

NAME
    API::Plesk - OO interface to the Plesk XML API
    (http://www.parallels.com/en/products/plesk/).

SYNOPSIS
        use API::Plesk;

        my $api = API::Plesk->new(
            username    => 'user', # required
            password    => 'pass', # required
            url         => 'https://127.0.0.1:8443/enterprise/control/agent.php', # required
            api_version => '1.6.3.1',
            debug       => 0,
            timeout     => 30,
        );

        my $res = $api->customer->get();

        if ($res->is_success) {
            for ( @{$res->data} ) {
                print "login: $_->{login}\n";
            }
        }
        else {
            print $res->error;
        }

DESCRIPTION
    At present the module provides interaction with Plesk 10.1 (API
    1.6.3.1). Distribution was completely rewritten and become more friendly
    for developers. Naming of packages and methods become similar to the
    same operators and operations of Plesk XML API.

    Partially implemented:

    API::Plesk::Customer

    API::Plesk::Database

    API::Plesk::DNS

    API:Plesk::FTPUser

    API:Plesk::Mail

    API::Plesk::ServicePlan

    API::Plesk::ServicePlanAddon

    API::Plesk::Site

    API::Plesk::SiteAlias

    API::Plesk::SiteBuilder

    API::Plesk::Webspace

    API::Plesk::WebUser

COMPATIBILITY WITH VERSION 1.*
    This is develover release. Comapatibility with Plesk::API 1.* is not
    implemented yet.

METHODS
    new(%params)
       Create new class instance.

       Required params: username password url

       Additional params: api_version - default 1.6.3.1 debug - default 0
       timeout - default 30 sec.

    send($operator, $operation, $data, %params)
       This method prepare and sends request to Plesk API.

       Returns API::Plesk::Response object.

       $operator - name of operator XML section of Plesk API.

       $operation - mane of operation XML section of Plesk API.

       $data - data hash that is converted to XML and is sended to plesk
       server.

    xml_http_req( $xml )
       Internal method. it implements real request sending to Plesk API.

       Returns array ( $response_xml, $error ).

SEE ALSO
    Plesk XML RPC API http://www.parallels.com/en/products/plesk/docs/

AUTHOR
    Odintsov Pavel <nrg[at]cpan.org>

    Nikolay Shulyakovskiy <shulyakovskiy[at]rambler.ru>

    Ivan Sokolov <ivsokolov[at]cpan.org>

COPYRIGHT AND LICENSE
    Copyright (C) 2008 by Ivan Sokolov

    This library is free software; you can redistribute it and/or modify it
    under the same terms as Perl itself, either Perl version 5.8.8 or, at
    your option, any later version of Perl 5 you may have available.

lib/API/Plesk.pm


package API::Plesk;

use strict;
use warnings;

use Carp;
use Data::Dumper;

use HTTP::Request;
use LWP::UserAgent;
use XML::Fast;
use version;

use API::Plesk::Response;

our $VERSION = '2.03';

# creates accessors to components
# can support old interface of API::Plesk
init_components(
    # new
    customer           => [['1.6.3.0', 'Customer']],
    webspace           => [['1.6.3.0', 'Webspace']],
    site               => [['1.6.3.0', 'Site']],
    subdomain          => [['1.6.3.0', 'Subdomain']],
    site_alias         => [['1.6.3.0', 'SiteAlias']],
    sitebuilder        => [['1.6.3.0', 'SiteBuilder']],
    ftp_user           => [['1.6.3.0', 'FTPUser']],
    service_plan       => [['1.6.3.0', 'ServicePlan']],
    service_plan_addon => [['1.6.3.0', 'ServicePlanAddon']],
    database           => [['1.6.3.0', 'Database']],
    webuser            => [['1.6.3.0', 'WebUser']],
    dns                => [['1.6.3.0', 'DNS']],
    mail               => [['1.6.3.0', 'Mail']],
    user               => [['1.6.3.0', 'User']],

    # old
    Accounts => [['1.5.0.0', 'Accounts']],
    Domains  => [['1.5.0.0', 'Domains']],
);

# constructor
sub new {
    my $class = shift;
    $class = ref ($class) || $class;

    my $self = {
        username    => '',
        password    => '',
        secret_key  => '',
        url         => '',
        api_version => '1.6.3.1',
        debug       => 0,
        timeout     => 30,
        (@_)
    };

    if (!$self->{secret_key}) {
        confess "Required username!" unless $self->{username};
        confess "Required password!" unless $self->{password};
    }
    confess "Required url!"      unless $self->{url};

    return bless $self, $class;
}

# sends request to Plesk API
sub send {
    my ( $self, $operator, $operation, $data, %params ) = @_;

    confess "Wrong request data!" unless $data && ref $data;

    my $xml = { $operator => { $operation => $data } };

    $xml = $self->render_xml($xml);

    warn "REQUEST $operator => $operation\n$xml" if $self->{debug};

    my ($response, $error) = $self->xml_http_req($xml);

    warn "RESPONSE $operator => $operation => $error\n$response" if $self->{debug};

    unless ( $error ) {
        $response = xml2hash $response, array => [$operation, 'result', 'property'];
    }

    return API::Plesk::Response->new(
        operator  => $operator,
        operation => $operation,
        response  => $response,
        error     => $error,
    );
}

sub bulk_send { confess "Not implemented!" }

# Send xml request to plesk api
sub xml_http_req {
    my ($self, $xml) = @_;

    # HTTP::Request undestends only bytes
    utf8::encode($xml) if utf8::is_utf8($xml);

    my $ua = new LWP::UserAgent( parse_head => 0 );
    my $req = new HTTP::Request POST => $self->{url};

    if ($self->{secret_key}) {
        $req->push_header(':KEY',  $self->{secret_key});
    } else {
        $req->push_header(':HTTP_AUTH_LOGIN',  $self->{username});
        $req->push_header(':HTTP_AUTH_PASSWD', $self->{password});
    }
    $req->content_type('text/xml; charset=UTF-8');
    $req->content($xml);

    # LWP6 hack to prevent verification of hostname
    $ua->ssl_opts(verify_hostname => 0) if $ua->can('ssl_opts');

    warn $req->as_string   if defined $self->{debug}  &&  $self->{debug} > 1;

    my $res = eval {
        local $SIG{ALRM} = sub { die "connection timeout" };
        alarm $self->{timeout};
        $ua->request($req);
    };
    alarm 0;

    warn $res->as_string   if defined $self->{debug}  &&  $self->{debug} > 1;

    return ('', 'connection timeout')
        if !$res || $@ || ref $res && $res->status_line =~ /connection timeout/;

    return $res->is_success() ?
        ($res->content(), '') :
        ('', $res->status_line);
}


# renders xml packet for request
sub render_xml {
    my ($self, $hash) = @_;

    my $xml = _render_xml($hash);

    $xml = qq|<?xml version="1.0" encoding="UTF-8"?><packet version="$self->{api_version}">$xml</packet>|;

    $xml;
}

# renders xml from hash
sub _render_xml {
    my ( $hash ) = @_;

    return $hash unless ref $hash;

    my $xml = '';

    for my $tag ( keys %$hash ) {
        my $value = $hash->{$tag};
        if ( ref $value eq 'HASH' ) {
            $value = _render_xml($value);
        }
        elsif ( ref $value eq 'ARRAY' ) {
            my $tmp;
            $tmp .= _render_xml($_) for ( @$value );
            $value = $tmp;
        }
        elsif ( ref $value eq 'CODE' ) {
            $value = _render_xml(&$value);
        }

        if ( !defined $value or $value eq '' ) {
            $xml .= "<$tag/>";
        }
        else {
            $xml .= "<$tag>$value</$tag>";
        }
    }

    $xml;
}


# initialize components
sub init_components {
    my ( %c ) = @_;
    my $caller = caller;

    for my $alias (  keys %c ) {

        my $classes = $c{$alias};

        my $sub = sub {
            my( $self ) = @_;
            $self->{"_$alias"} ||= $self->load_component($classes);
            return $self->{"_$alias"} || confess "Not implemented!";
        };

        no strict 'refs';

        *{"$caller\::$alias"} = $sub;


    }

}

# loads component package and creates object
sub load_component {
    my ( $self, $classes ) = @_;
    my $version = version->parse($self->{api_version});

    for my $item ( @$classes ) {

        # select compitable version of component
        if ( $version >= $item->[0] ) {

            my $pkg = 'API::Plesk::' . $item->[1];

            my $module = "$pkg.pm";
               $module =~ s/::/\//g;

            local $@;
            eval { require $module };
            if ( $@ ) {
                confess "Failed to load $pkg: $@";
            }

            return $pkg->new(plesk => $self);

        }

    }

}

1;

__END__

=head1 NAME

API::Plesk - OO interface to the Plesk XML API (http://www.parallels.com/en/products/plesk/).

=head1 SYNOPSIS

    use API::Plesk;

    my $api = API::Plesk->new(
        username    => 'user', # required
        password    => 'pass', # required
        url         => 'https://127.0.0.1:8443/enterprise/control/agent.php', # required
        api_version => '1.6.3.1',
        debug       => 0,
        timeout     => 30,
    );

    my $res = $api->customer->get();

    if ($res->is_success) {
        for ( @{$res->data} ) {
            print "login: $_->{login}\n";
        }
    }
    else {
        print $res->error;
    }

=head1 DESCRIPTION

At present the module provides interaction with Plesk 10.1 (API 1.6.3.1).
Distribution was completely rewritten and become more friendly for developers.
Naming of packages and methods become similar to the same operators and operations of Plesk XML API.

Partially implemented:

API::Plesk::Customer

API::Plesk::Database

API::Plesk::DNS

API:Plesk::FTPUser

API:Plesk::Mail

API::Plesk::ServicePlan

API::Plesk::ServicePlanAddon

API::Plesk::Site

API::Plesk::SiteAlias

API::Plesk::SiteBuilder

API::Plesk::Webspace

API::Plesk::WebUser

API::Plesk::User

=head1 COMPATIBILITY WITH VERSION 1.*

This is develover release. Comapatibility with Plesk::API 1.* is not implemented yet.

=head1 METHODS

=over 3

=item new(%params)

Create new class instance.

Required params:
username
password
url

Additional params:
api_version - default 1.6.3.1
debug       - default 0
timeout     - default 30 sec.

=item send($operator, $operation, $data, %params)

This method prepare and sends request to Plesk API.

Returns API::Plesk::Response object.

$operator - name of operator XML section of Plesk API.

$operation - mane of operation XML section of Plesk API.

$data - data hash that is converted to XML and is sended to plesk server.

=item xml_http_req( $xml )

Internal method. it implements real request sending to Plesk API.

Returns array ( $response_xml, $error ).

=back

=head1 SEE ALSO

Plesk XML RPC API  http://www.parallels.com/en/products/plesk/docs/

=head1 AUTHORS

Odintsov Pavel E<lt>nrg[at]cpan.orgE<gt>

Ivan Sokolov E<lt>ivsokolov[at]cpan.orgE<gt>

B<Thanks for contribution:>

Nikolay Shulyakovskiy E<lt>shulyakovskiy[at]rambler.ruE<gt>

bgmilne

Eugeny Zavarykin

Eugen Konkov

Ivan Shamal

Akzhan Abdulin

Jari Turkia

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2008 by Ivan Sokolov

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.

=cut

lib/API/Plesk/Component.pm

package API::Plesk::Component;

use strict;
use warnings;

use Carp;

sub new {
    my ( $class, %attrs ) = @_;
    $class = ref $class || $class;

    confess "Required API::Plesk object!" unless $attrs{plesk};

    return bless \%attrs, $class;
}

# API::Plesk object
sub plesk { $_[0]->{plesk} }

sub check_required_params {
    my ( $self, $hash, @fields ) = @_;
    
    for my $key ( @fields ) {
        if ( ref $key ) {
            confess "Required any of this fields: " . join( ", ", @$key) . "!"
                unless grep { $hash->{$_} } @$key;
        } else {
            confess "Required field $key!" unless exists $hash->{$key};
        }
    }
}

# sort params in right order
sub sort_params {
    my ( $self, $params, @fields ) = @_;

    my @sorted;
    for my $key ( @fields ) {

        if ( ref $key ) {
            ($key) = grep { exists $params->{$_} } @$key 
        }
        push @sorted, {$key => $params->{$key}}
            if exists $params->{$key};

    }

    return \@sorted;
}

# check hosting xml section
sub check_hosting {
    my ( $self, $params, $required ) = @_;

    unless ( $params->{hosting} ) {
        confess "Required hosting!" if $required;
        return;
    }

    my $hosting = $params->{hosting};
    my $type = delete $hosting->{type};
    my $ip = delete $hosting->{ip_address};
    
    #confess "Required ip_address" unless $ip;
    
    if ( $type eq 'vrt_hst' ) {

        $self->check_required_params($hosting, qw(ftp_login ftp_password));

        my @properties;
        for my $key ( sort keys %$hosting ) {
            push @properties, { property => [
                {name => $key}, 
                {value => $hosting->{$key}} 
            ]};
            delete $hosting->{$key};
        }
        push(@properties, { ip_address => $ip }) if $ip;
        $hosting->{$type} = @properties ? \@properties : '';

        return;
    }

    elsif ( $type eq 'std_fwd' or $type eq 'frm_fwd' ) {
        
        confess "Required dest_url field!" unless $hosting->{dest_url};
        
        $hosting->{$type} = {
            dest_url => delete $hosting->{dest_url},
        };
        $hosting->{$type}->{ip_address} = $ip if $ip;

        return;
    }
    elsif ( $type eq 'none' ) {
        $hosting->{$type} = '';
        return;
    }

    confess "Unknown hosting type!";
}

sub prepare_filter {
    my ( $self, $filter, %opts ) = @_;

    my @filter;
    my $sort = $opts{sort_keys} || [keys %$filter];

    for my $key ( @$sort ) {
        if ( ref $filter->{$key} eq 'ARRAY' ) {
            for my $value ( @{$filter->{$key}} ) {
                push @filter, { $key => $value };
            }
        }
        else {
            push @filter, { $key => $filter->{$key} };
        }
    }

    return @filter ? \@filter : '';
}

1;

__END__

=head1 NAME

API::Plesk::Component -  Base class for components.

=head1 SYNOPSIS

package API::Plesk::Customer;

use base 'API::Plesk::Component';

sub get { ... }
sub set { ... }

1;

=head1 DESCRIPTION

Base class for components.

=head1 METHODS

=over 3

=item new(plesk => API::Plesk->new(...))

Create component object.

=item plesk()

    Referer to API::Plesk object.

=item

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/Customer.pm


package API::Plesk::Customer;

use strict;
use warnings;

use Carp;

use base 'API::Plesk::Component';

my @gen_info_fields = qw(
    cname
    pname
    login
    passwd
    status
    phone
    fax
    email
    address 
    city 
    state
    pcode
    country 
    owner-id
);

sub add {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};
    my $gen_info  = $params{gen_info} || confess "Required gen_info parameter!";

    $self->check_required_params($gen_info, qw(pname login passwd));
    
    my $data = {
        gen_info => $self->sort_params($params{gen_info}, @gen_info_fields)
    };

    return $bulk_send ? $data : 
        $self->plesk->send('customer', 'add', $data);
}

sub get {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};

    my $data = { 
        filter => @_ > 2 ? \%filter : '',
        dataset => [ {gen_info => ''}, {stat => ''} ]
    };

    return $bulk_send ? $data : 
        $self->plesk->send('customer', 'get', $data);
}

sub set {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};
    my $filter    = $params{filter}   || '';
    my $gen_info  = $params{gen_info} || '';

    $gen_info || confess "Required gen_info or stat parameter!";

    my $data = {
        filter  => $filter,
        values => {
            gen_info => $gen_info,
        }
    };

    return $bulk_send ? $data :
        $self->plesk->send('customer', 'set', $data);
}

sub del {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};

    my $data = {
        filter  => @_ > 2 ? \%filter : ''
    };

    return $bulk_send ? $data : 
        $self->plesk->send('customer', 'del', $data);
}

1;

__END__

=head1 NAME

API::Plesk::Customer -  Managing customer accounts.

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->customer->add(..);
    $response = $api->customer->get(..);
    $response = $api->customer->set(..);
    $response = $api->customer->del(..);

=head1 DESCRIPTION

Module manage customer accounts.

=head1 METHODS

=over 3

=item add(%params)

Method adds customer to Plesk Panel.

    %params = (
        # required
        gen_info => {
            pname => 'Mike',
            login => 'mike',
            passwd => '12345',
            ...            
        }
    );

=item get(%params)

Method gets customer data.

    %params = (
        filter => {...}
    );

=item set(%params)

Method sets customer data.

    %params = (
        filter   => {...},
        gen_info => {...}
    );

=item del(%params)

Method deletes customer from Plesk Panel.

    %params = (
        filter => {...}
    );

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/DNS.pm


package API::Plesk::DNS;

use strict;
use warnings;

use Carp;

use base 'API::Plesk::Component';

sub add_rec {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};

    my @fields = (
        [qw(site-id site-alias-id)],
        'type',
        'host',
        'value'
    );

    $self->check_required_params(\%params, @fields);
    my $data = $self->sort_params(\%params, @fields, 'opt');

    return $bulk_send ? $data : 
        $self->plesk->send('dns', 'add_rec', $data);
}

sub get_rec {
    my ( $self, %filter ) = @_;
    my $bulk_send = delete $filter{bulk_send};
    my $template = delete $filter{template};
    

    my $data = [
        { filter  => @_ > 2 ? \%filter : '' },
        ( $template ? {template => $template} : () ),
    ];

    return $bulk_send ? $data : 
        $self->plesk->send('dns', 'get_rec', $data);
}

sub del_rec {
    my ( $self, %filter ) = @_;
    my $bulk_send = delete $filter{bulk_send};
    my $template = delete $filter{template};

    my $data = [
        { filter  => $self->prepare_filter(\%filter) },
        ( $template ? {template => $template} : () ),
    ];

    return $bulk_send ? $data : 
        $self->plesk->send('dns', 'del_rec', $data);
}

sub get_soa {
    my ( $self, %filter ) = @_;
    my $bulk_send = delete $filter{bulk_send};

    my $data = [
        { filter  => @_ > 2 ? \%filter : '' }
    ];

    return $bulk_send ? $data : 
        $self->plesk->send('dns', 'get', $data);
}

1;

lib/API/Plesk/Database.pm


package API::Plesk::Database;

use strict;
use warnings;

use Carp;

use base 'API::Plesk::Component';

sub add_db {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};

    $self->check_required_params(\%params, qw(webspace-id name type));
     
    return $bulk_send ? \%params : 
        $self->plesk->send('database', 'add-db', \%params);
}

sub del_db {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};

    my $data = {
        filter  => @_ > 2 ? \%filter : ''
    };

    return $bulk_send ? $data : 
        $self->plesk->send('database', 'del-db', $data);
}

sub add_db_user {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};

    $self->check_required_params(\%params, qw(db-id login password));

    my $data = $self->sort_params(\%params, qw(db-id login password));
 
    return $bulk_send ? $data : 
        $self->plesk->send('database', 'add-db-user', $data);
}

sub del_db_user {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};

    my $data = {
        filter  => @_ > 2 ? \%filter : ''
    };

    return $bulk_send ? $data : 
        $self->plesk->send('database', 'del-db-user', $data);
}

1;

__END__

=head1 NAME

API::Plesk::Database -  Managing databases.

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->database->add_db(..);
    $response = $api->database->del_db(..);
    $response = $api->database->add_db_user(..);
    $response = $api->database->del_db_user(..);

=head1 DESCRIPTION

Module manage databases and database users.

=head1 METHODS

=over 3

=item add_db(%params)

=item del_db(%params)

=item add_db_user(%params)

=item del_db_user(%params)

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/FTPUser.pm


package API::Plesk::FTPUser;

use strict;
use warnings;

use Carp;
use Data::Dumper;

use base 'API::Plesk::Component';

#TODO
sub add {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};

    my @sort_fields = (
        'name',
        'password',
        'home',
        'create_non_existent',
        'quota',
        'permissions',
        [qw(site-id site-name)],        
    );
    my @required_fields = (
        'name',
        'password',
        [qw(site-id site-name)],        
    );

    $self->check_required_params(\%params, @required_fields);
    
    my $data = $self->sort_params(\%params, @sort_fields);

    return $bulk_send ? $data : 
        $self->plesk->send('ftp-user', 'add', $data);
}

sub get {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};
    my $data = { 
        filter  => @_ > 2 ? \%filter : '',
    };

    return $bulk_send ? $data : 
        $self->plesk->send('ftp-user', 'get', $data);
}

sub set {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send}; 
    my $filter    = delete $params{filter} || '';
   
     my @sort_fields = (
        'name',
        'password',
        'home',
        'create_non_existent',
        'quota',
        'permissions',
    );

    my $data = {
        filter  => $filter,
        values  => $self->sort_params(\%params, @sort_fields),
    };

    return $bulk_send ? $data : 
        $self->plesk->send('ftp-user', 'set', $data);
}

sub del {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send}; 

    my $data = {
        filter  => @_ > 2 ? \%filter : ''
    };

    return $bulk_send ? $data : 
        $self->plesk->send('ftl-user', 'del', $data);
}

1;

__END__

=head1 NAME

API::Plesk::Site -  Managing sites (domains).

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->site->get(..);

=head1 DESCRIPTION

Module manage sites (domains).

=head1 METHODS

=over 3

=item add(%params)

=item get(%params)

=item set(%params)

=item del(%params)

=item get_physical_hosting_descriptor(%params)

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/Mail.pm


package API::Plesk::Mail;

use strict;
use warnings;

use Carp;
use Data::Dumper;

use base 'API::Plesk::Component';

sub enable {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send}; 

    return $bulk_send ? \%filter : 
        $self->plesk->send('mail', 'enable', \%filter);
}

sub disable {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send}; 

    return $bulk_send ? \%filter : 
        $self->plesk->send('mail', 'disable', \%filter);
}


1;

__END__

=head1 NAME

API::Plesk::Mail -  Managing mail on Domain Level.

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->mail->enable(..);

=head1 DESCRIPTION

Module manage mail on Domain Level.

=head1 METHODS

=over 3

=item enable(%params)

=item disable(%params)

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/Mock.pm


package API::Plesk::Mock;

use strict;
use warnings;

use base 'API::Plesk';

sub mock_response {
    $_[0]->{mock_response} = $_[1] if @_ > 1;
    $_[0]->{mock_response};
}

sub mock_error {
    $_[0]->{mock_error} = $_[1] if @_ > 1;
    $_[0]->{mock_error};
}

sub xml_http_req { ($_[0]->{mock_response}, $_[0]->{mock_error}) }

1;

__END__

=head1 NAME

API::Plesk::Mock - Module for testing API::Plesk without sending real requests to Plesk API.

=head1 SYNOPSIS

    use API::Plesk::Mock;

    my $api = API::Plesk::Mock->new(
        username    => 'user', # required
        password    => 'pass', # required
        url         => 'https://127.0.0.1:8443/enterprise/control/agent.php', # required
        api_version => '1.6.3.1',
        debug       => 0,
        timeout     => 30,
    );
    $api->mock_response($some_response_xml);
    $api->mock_error($some_error_text);

=head1 DESCRIPTION

Module for testing API::Plesk without sending real requests to Plesk API.

=head1 METHODS

=over 3

=item mock_response($xml)

Sets response from Plesk API

=item mock_error($text)

Sets any error

=back

=head1 AUTHOR

Ivan Sokolov E<lt>ivsokolov[at]cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2008 by Ivan Sokolov

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.

=cut

lib/API/Plesk/Response.pm


package API::Plesk::Response;

use strict;
use warnings;

use Data::Dumper;

sub new {
    my ( $class, %attrs) = @_;
    $class = ref $class || $class;

    my $operator  = $attrs{operator};
    my $operation = $attrs{operation};
    my $response  = $attrs{response};
    my $results = [];
    my $is_success = 1;

    # internal API::Plesk error
    if ( $attrs{error} ) {
        $results = [{
            errcode => '',
            errtext => $attrs{error},
            status  => 'error'
        }];
        $is_success = '';
    }
    # remote system plesk error
    elsif ( exists $response->{packet}->{'system'} ) {
        $results = [$response->{packet}->{'system'}];
        $is_success = '';
        $operator   = 'system';
        $operation  = '';
    }
    else {
        eval {
            for my $result ( @{$response->{packet}->{$operator}->{$operation}->[0]->{result}} ) {
                push @$results, $result;
                $is_success = '' if $result->{status} && $result->{status} eq 'error';
            }
            1;
        } || do {
            $results = [{
                errcode => '',
                errtext => "Internal Plesk error: $_.\nError: $@\nDetails:" . Dumper( $response ),
                status  => 'error'
            }];
        };
    }

    my $self = {
        results     => $results,
        operator   => $operator,
        operation  => $operation,
        is_success => $is_success,
    };

    return bless $self, $class;
}

sub is_success { $_[0]->{is_success} }

sub id   { $_[0]->{results}->[0]->{id} }
sub guid { $_[0]->{results}->[0]->{guid} }

sub data {
    my ( $self ) = @_;
    return [] unless $self->is_success;
    return [ map { $_->{data} || () } @{$self->{results}} ];
}

sub results {
    my ( $self ) = @_;
    return   unless $self->is_success;
    return $self->{results} || [];
}

sub error_code { $_[0]->error_codes->[0]; }
sub error_text { $_[0]->error_texts->[0]; }

sub error {
    my ( $self ) = @_;
    return ($self->{results}->[0]->{errcode} || '0') . ': ' .  $self->{results}->[0]->{errtext};
}

sub error_codes {
    my ( $self ) = @_;
    return [] if $self->is_success;
    return [ map { $_->{errcode} || () } @{$self->{results}} ];
}

sub error_texts {
    my ( $self ) = @_;
    return [] if $self->is_success;
    return [ map { $_->{errtext} || () } @{$self->{results}} ];
}

sub errors {
    my ( $self ) = @_;
    return [] if $self->is_success;
    my @errors;
    for ( @{$self->{results}} ) {
        my $error = ($_->{errcode} || '0') . ': ' .  $_->{errtext};
        push @errors, $error;
    }
    return \@errors;
}

sub is_connection_error {
    my ( $self ) = @_;

    return
        $self->error_text =~ /connection failed/ ||
        $self->error_text =~ /connection timeout/ ||
        $self->error_text =~ /500\s+/
            ? 1 : 0;
}

1;

__END__

=head1 NAME

API::Plesk::Response -  Class for processing server answers with errors handling.

=head1 SYNOPSIS

    my $res = API::Plesk::Response->new(
        operator => 'customer',
        operation => 'get',
        response => 'xml answer from plesk api',
    );

    $res->is_success;
    $res->is_connection_error;

    # get errors
    $res->error_code;
    $res->error_codes->[0];
    $res->error_text;
    $res->error_texts->[0];
    $res->error;
    $res->errors->[0];

    # get data sections
    $res->data->[0];

    # get result sections
    $res->results->[0];

    # get id and guid
    $res->id;
    $res->guid;


=head1 DESCRIPTION

This class is intended for convenient processing results of Plesk API responses.
Every operation of API::Plesk::Component return object of this class.
And it get you easy way to manipulate with response from Plesk API.

=head1 METHODS

=over 3

=item new(%attributes)

Create response object.

=item is_success()

Returns true if all results have no errors.

=item is_connection_error()

Returns true if connection error happened.

=item data()

    $response->data;
    $response->data->[0];

=item results()

    $response->results;
    $response->results->[0];

=item error_code()

    $response->error_code;

=item error_codes()

    $response->error_codes->[0];

=item error_text()

    $response->error_text;

=item error_texts()

    $response->error_texts->[0];

=item error()

    $response->error;

=item errors()

    $response->errors->[0];

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/ServicePlan.pm


package API::Plesk::ServicePlan;

use strict;
use warnings;

use Carp;
use Data::Dumper;
use base 'API::Plesk::Component';

my @header_fields = qw(
    owner-id
    owner-login
);

my @other_fields = qw(
    mail
    limits
    log-rotation
    preferences
    hosting
    performance
    permissions
    external-id
    name
);

my @main_fields = ( @header_fields, @other_fields );

sub get {
    my ($self, %params) = @_;
    my $bulk_send = delete $params{bulk_send};

    my $filter = delete $params{filter} || '';
    my $data = [
        { filter => $filter },
        @{ $self->sort_params( \%params, @main_fields ) },
    ];

    return $bulk_send ? $data : 
        $self->plesk->send('service-plan', 'get', $data);
}

sub set {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send}; 
    my $filter    = delete $params{filter} || '';
    
    $self->check_hosting(\%params);

    my $data = [
        { filter  => $filter },
        @{$self->sort_params(\%params, @main_fields)},
    ];

    return $bulk_send ? $data : 
        $self->plesk->send('service-plan', 'set', $data);
}

sub del {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send}; 

    my $data = {
        filter  => @_ > 2 ? \%filter : ''
    };

    return $bulk_send ? $data : 
        $self->plesk->send('service-plan', 'del', $data);
}

1;

__END__

=head1 NAME

API::Plesk::ServicePlan -  Managing service plans.

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->service_plan->get(...);
    $response = $api->service_plan->set(...);
    $response = $api->service_plan->del(...);

=head1 DESCRIPTION

Module manage service plans.

=head1 METHODS

=over 3

=item get(%params)

=item setarams)

=item del(%params)

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/ServicePlanAddon.pm


package API::Plesk::ServicePlanAddon;

use strict;
use warnings;

use Carp;

use base 'API::Plesk::Component';

my @main_fields = qw/
    owner-id
    owner-login
/;

sub get {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};

    my $filter = delete $params{filter} || '';
    my $data = [
        { filter => $filter },
        @{ $self->sort_params( \%params, @main_fields ) },
    ];

    return $bulk_send ? $data : 
        $self->plesk->send('service-plan-addon', 'get', $data);
}

1;

__END__

=head1 NAME

API::Plesk::ServicePlanAddon -  Managing add-on plans.

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->service_addon_plan->get(..);

=head1 DESCRIPTION

Module manage add-on plans.

=head1 METHODS

=over 3

=item get(%params)

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/Site.pm


package API::Plesk::Site;

use strict;
use warnings;

use Carp;
use Data::Dumper;

use base 'API::Plesk::Component';

#TODO
sub add {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};
    my $gen_setup = $params{gen_setup} || confess "Required gen_setup parameter!";

    my @fields = (
        'name',
        [qw(webspace-name webspace-id webspace-guid)]
    );

    $self->check_required_params($gen_setup, @fields);
    $self->check_hosting(\%params);

    $params{gen_setup} = $self->sort_params($gen_setup, @fields);
    
    my $data = $self->sort_params(\%params, qw(gen_setup hosting prefs));

    return $bulk_send ? $data : 
        $self->plesk->send('site', 'add', $data);
}

sub get {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};
    my $dataset   = {gen_info => ''};
    
    if ( my $add = delete $filter{dataset} ) {
        $dataset = { map { ( $_ => '' ) } ref $add ? @$add : ($add) };
        $dataset->{gen_info} = '';
    }

    my $data = { 
        filter  => @_ > 2 ? \%filter : '',
        dataset => $dataset,
    };

    return $bulk_send ? $data : 
        $self->plesk->send('site', 'get', $data);
}

sub set {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send}; 
    my $filter    = delete $params{filter} || '';
    
    $self->check_hosting(\%params);


    my $data = {
        filter  => $filter,
        values  => $self->sort_params(\%params, qw(gen_setup prefs hosting disk_usage)),
    };

    return $bulk_send ? $data : 
        $self->plesk->send('site', 'set', $data);
}

sub del {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send}; 

    my $data = {
        filter  => @_ > 2 ? \%filter : ''
    };

    return $bulk_send ? $data : 
        $self->plesk->send('site', 'del', $data);
}

sub get_physical_hosting_descriptor {
    my ( $self, %filter ) = @_;
    my $bulk_send = delete $filter{bulk_send};
    
    my $data = {
        filter  => @_ > 2 ? \%filter : ''
    };

    return $bulk_send ? $data :
        $self->plesk->send(
            'site', 
            'get-physical-hosting-descriptor', 
            $data
        );
}

1;

__END__

=head1 NAME

API::Plesk::Site -  Managing sites (domains).

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->site->get(..);

=head1 DESCRIPTION

Module manage sites (domains).

=head1 METHODS

=over 3

=item add(%params)

=item get(%params)

=item set(%params)

=item del(%params)

=item get_physical_hosting_descriptor(%params)

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/SiteAlias.pm


package API::Plesk::SiteAlias;

use strict;
use warnings;

use Carp;

use base 'API::Plesk::Component';

#TODO
sub create {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};

    $self->check_required_params(\%params, [qw(site-id name)]);

    return $bulk_send ? \%params : 
        $self->plesk->send('site-alias', 'create', \%params);
}

sub get {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};

    my $data = { 
        filter  => @_ > 2 ? \%filter : '',
    };

    return $bulk_send ? $data : 
        $self->plesk->send('site-alias', 'get', $data);
}

sub set {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send}; 
    
    return $bulk_send ? \%params : 
        $self->plesk->send('site-alias', 'set', \%params);
}

sub del {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send}; 

    my $data = {
        filter  => @_ > 2 ? \%filter : ''
    };

    return $bulk_send ? $data : 
        $self->plesk->send('site-alias', 'del', $data);
}

1;

__END__

=head1 NAME

API::Plesk::SiteAlias -  Managing site aliases.

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->site_alias->get(..);

=head1 DESCRIPTION

Module manage sites (domains).

=head1 METHODS

=over 3

=item create(%params)

=item get(%params)

=item set(%params)

=item del(%params)

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/SiteBuilder.pm


package API::Plesk::SiteBuilder;

use strict;
use warnings;

use Carp;
use Data::Dumper;

use base 'API::Plesk::Component';

#TODO
sub assign_trial_site {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};

    $self->check_required_params(\%params, qw(pp-site-guid sb-site-uuid));
    
    return $bulk_send ? \%params : 
        $self->plesk->send('sitebuilder', 'assign-trial-site', \%params);
}

1;

__END__

=head1 NAME

API::Plesk::SiteBuilder -  Managing SiteBuilder sites.

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->sitebuilder->assign_trial_site(...);

=head1 DESCRIPTION

Module manage SiteBuilder sites.

=head1 METHODS

=over 3

=item assign_trial_site(%params)

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/User.pm


package API::Plesk::User;

use strict;
use warnings;

use Carp;

use base 'API::Plesk::Component';

my @gen_info_fields = qw(
    cname
    login
    passwd
    owner-guid
    owner-external-id
    name
    contact-info
    status
    external-id
);

sub add {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};
    my $gen_info  = $params{gen_info} || confess "Required gen_info parameter!";
    my $roles  = $params{roles} || confess "Required roles parameter!";

    $self->check_required_params($gen_info, qw(name login passwd));
    
    my $unsorteddata = {
        'gen-info' => $self->sort_params($params{gen_info}, @gen_info_fields),
        roles => $roles,
    };
    my $data = $self->sort_params($unsorteddata, qw(gen-info roles));

    return $bulk_send ? $data : 
        $self->plesk->send('user', 'add', $data);
}

sub get {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};

    my $data = { 
        filter => @_ > 2 ? \%filter : '',
        dataset => [ {'gen-info' => ''}, {roles => ''} ]
    };

    return $bulk_send ? $data : 
        $self->plesk->send('user', 'get', $data);
}

sub set {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};
    my $filter    = $params{filter}   || '';
    my $gen_info  = $params{gen_info} || '';

    $gen_info || confess "Required gen_info or stat parameter!";

    my $data = {
        filter  => $filter,
        values => {
            gen_info => $gen_info,
        }
    };

    return $bulk_send ? $data :
        $self->plesk->send('user', 'set', $data);
}

sub del {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};

    my $data = {
        filter  => @_ > 2 ? \%filter : ''
    };

    return $bulk_send ? $data : 
        $self->plesk->send('user', 'del', $data);
}

1;

__END__

=head1 NAME

API::Plesk::Customer -  Managing user (e.g. auxiliary) accounts.

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->user->add(..);
    $response = $api->user->get(..);
    $response = $api->user->set(..);
    $response = $api->user->del(..);

=head1 DESCRIPTION

Module manage user (e.g. auxiliary) accounts.

Filters used by get,del etc. are as follows:
%filter => {
        guid => xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
        # or
        owner-guid => xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
        # or
        external-id => xx
        # or
        owner-external-id => xx
}


=head1 METHODS

=over 3

=item add(%params)

Method adds users to Plesk Panel.

    %params = (
        # required
        gen_info => {
            login => 'mike', # required
            passwd => '12345', # required
            name => 'Mike', # required
            owner-guid => # one of this or
            owner-external-id => # this required
            ...    
        }
        # required
        roles => {
            name => 'WebMaster',
            ...
    );

=item get(%params)

Method gets user data.

    %params = ( %filter );

=item set(%params)

Method sets user data.

    %params = (
        filter   => {...},
        gen_info => {...}
    );

=item del(%params)

Method deletes user from Plesk Panel.

    %params = ( %filter );

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/WebUser.pm


package API::Plesk::WebUser;

use strict;
use warnings;

use Carp;
use Data::Dumper;

use base 'API::Plesk::Component';

#TODO
sub add {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};

    $self->check_required_params(\%params, qw(site-id login));

    my $data = $self->sort_params(\%params, qw(site-id login password password-type ftp-quota services));

    return $bulk_send ? $data : 
        $self->plesk->send('webuser', 'add', $data);
}

sub get {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};

    my $data = { 
        filter  => @_ > 2 ? \%filter : '',
    };

    return $bulk_send ? $data : 
        $self->plesk->send('webuser', 'get', $data);
}

sub get_prefs {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};

    my $data = { 
        filter  => @_ > 2 ? \%filter : '',
    };

    return $bulk_send ? $data : 
        $self->plesk->send('webuser', 'get-prefs', $data);
}

sub set {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send}; 
    my $filter    = delete $params{filter} || '';
    
    my $data = {
        filter  => $filter,
        values  => $self->sort_params(\%params, qw(password password-type ftp-quota services)),
    };

    return $bulk_send ? $data : 
        $self->plesk->send('webuser', 'set', $data);
}

sub del {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send}; 

    my $data = {
        filter  => @_ > 2 ? \%filter : ''
    };

    return $bulk_send ? $data : 
        $self->plesk->send('webuser', 'del', $data);
}

1;

__END__

=head1 NAME

API::Plesk::WebUser -  Managing webusers.

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->webuser->get(..);

=head1 DESCRIPTION

Module manage webusers.

=head1 METHODS

=over 3

=item add(%params)

=item get(%params)

=item get_prefs(%params)

=item set(%params)

=item del(%params)

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

lib/API/Plesk/Webspace.pm


package API::Plesk::Webspace;

use strict;
use warnings;

use Carp;
use Data::Dumper;

use base 'API::Plesk::Component';

my @gen_setup_fields = qw(
    name
    owner-id
    owner-login
    owner-guid
    owner-external-id
    htype
    ip_address
    status
    external-id
);

my @main_fields = qw(
    gen_setup
    hosting    
    limits     
    prefs      
    performance
    permissions
    plan-id
    plan-name
    plan-guid
    plan-external-id
);

sub add {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send};
    my $gen_setup = $params{gen_setup} || confess "Required gen_setup parameter!";

    $self->check_hosting(\%params);

    $self->check_required_params(\%params, [qw(plan-id plan-name plan-guid plan-external-id)]);
    $self->check_required_params($gen_setup, qw(name ip_address));

    $params{gen_setup} = $self->sort_params($gen_setup, @gen_setup_fields);
    
    my $data = $self->sort_params(\%params, @main_fields);

    return $bulk_send ? $data :
        $self->plesk->send('webspace', 'add', $data);
}

sub get {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send};
    my $dataset   = {gen_info => ''};
    
    if ( my $add = delete $filter{dataset} ) {
        $dataset = { map { ( $_ => '' ) } ref $add ? @$add : ($add) };
        $dataset->{gen_info} = '';
    }

    my $data = { 
        filter  => @_ > 2 ? \%filter : '',
        dataset => $dataset,
    };

    return $bulk_send ? $data : 
        $self->plesk->send('webspace', 'get', $data);
}

sub set {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send}; 
    my $filter    = delete $params{filter} || '';
    my $gen_setup = $params{gen_setup};
    
    $params{gen_setup} = $self->sort_params($gen_setup, @gen_setup_fields) if $gen_setup;
    $self->check_hosting(\%params);

    my $data = [
        { filter  => $filter },
        { values  => $self->sort_params(\%params, @main_fields) },
    ];

    return $bulk_send ? $data : 
        $self->plesk->send('webspace', 'set', $data);
}

sub del {
    my ($self, %filter) = @_;
    my $bulk_send = delete $filter{bulk_send}; 

    my $data = {
        filter  => @_ > 2 ? \%filter : ''
    };

    return $bulk_send ? $data : 
        $self->plesk->send('webspace', 'del', $data);
}

sub add_plan_item {
    my ( $self, %params ) = @_;
    my $bulk_send = delete $params{bulk_send}; 
    my $filter    = delete $params{filter} || '';

    my $name = $params{name} || confess "Required name field!";    
    my $data = {
        filter      => $filter,
        'plan-item' => { name => $name },
    };

    return $bulk_send ? $data : 
        $self->plesk->send('webspace', 'add-plan-item', $data);
}

sub add_subscription {
    my ($self, %params) = @_;
    my $bulk_send = delete $params{bulk_send}; 
    
    $self->check_required_params(\%params, [qw(plan-guid plan-external-id)]);

    my $data = $self->sort_params(\%params, 'filter', [qw(plan-guid plan-external-id)]);

    return $bulk_send ? $data : 
        $self->plesk->send('webspace', 'add-subscription', $data);
}

sub remove_subscription {
    my ($self, %params) = @_;
    my $bulk_send = delete $params{bulk_send}; 
    
    $params{filter} ||= '';

    $self->check_required_params(\%params, [qw(plan-guid plan-external-id)]);

    my $data = $self->sort_params(\%params, 'filter', [qw(plan-guid plan-external-id)]);

    return $bulk_send ? $data : 
        $self->plesk->send('webspace', 'remove-subscription', $data);
}

sub switch_subscription {
    my ($self, %params) = @_;
    my $bulk_send = delete $params{bulk_send}; 
    
    $params{filter} ||= '';

    $self->check_required_params(\%params, [qw(plan-guid plan-external-id no-plan)]);

    my $data = $self->sort_params(\%params, 'filter', [qw(plan-guid plan-external-id no-plan)]);

    return $bulk_send ? $data : 
        $self->plesk->send('webspace', 'switch-subscription', $data);
}


1;

__END__

=head1 NAME

API::Plesk::Webspace -  Managing subscriptions (webspaces).

=head1 SYNOPSIS

    $api = API::Plesk->new(...);
    $response = $api->webspace->add(..);
    $response = $api->webspace->del(..);

=head1 DESCRIPTION

Module manage subscriptions (webspaces).

=head1 METHODS

=over 3

=item add(%params)

=item get(%params)

=item set(%params)

=item del(%params)

=item add_plan_item(%params)

=item add_subscription(%params)

=item remove_subscription(%params)

=item switch_subscription(%params)

=back

=head1 AUTHOR

Ivan Sokolov <lt>ivsokolov@cpan.org<gt>

=cut

t/TestData.pm

package TestData;

use Test::More;
use API::Plesk;

#
# Support sub`s and data for tests
#

our @EXPORT = qw(
    delete_all_accounts
    create_unique_user_data
    _compare
    gen_passwd
    iterate
    check_input_data
    create_work_logins
);

# ONLINE configs

my $online_plesk_url = 'https://127.0.0.1:8443/enterprise/control/agent.php';

our $online_manual_created_template_name = $ENV{'template_name'};
our $online_manual_created_template_id  = $ENV{'template_id'};
our $online_new_tariff_name = $ENV{'new_tariff'};

our %online_plesk_valid_params = (
    api_version   => $ENV{'plesk_api_version'}  || '1.5.0.0',
    username      => $ENV{'plesk_username'}     || 'admin',
    password      => $ENV{'plesk_password'}     || 'qwerty',
    url           => $ENV{'plesk_url'}          || $online_plesk_url,
    debug         => '',
);

# END online  configs

our $test_user_data =  { 
    pname   => 'pavel', 
    login   => 'pav_perl',
    passwd  => 'password',
    phone   => '8 555 1111111',
    email   => 'nrg@nrg.name',
    country => 'RU'                 # optional field
};


our $stress_test_data = {
    pname => [ # 1 .. 60 symbols 
        'pavel',
        '2342134123412',
        'dasdf_dasdsa_ADdssd__DASsd',
        # '____', error
        (join '', 'x' x 60),
        'add_(efewf)_(sdfsd)',
    ],
    login => [ # 1 .. 20 symbols, 
        'pav_perl',
        # '___', error
        '123546',
        #'_sdad',
        'nrg.name',
        'test.code.google.com',
        'yet-another-test',
        (join q//, 'x' x 20),
        # '_(fsdf)_(fsadf)' error
    ],
    passwd => [
        'five!',
        (join q//, 'x' x 14),
        #'##$^$#&^%!@#$d',
        12314321,
        '_(sdf)%!Sas',
        gen_passwd(),
        gen_passwd(),
        gen_passwd()
        #'тестовый' error
    ],
    phone => [ # 0 .. 30
        '8 555 1111111',
        # 'abcdefaff', error
        '5',
        '+7 499 123 44 55',
        '+74991234455',
        '',
        ' ',
        '8(846)1234356',
        '+7(846)1234356',
        # '()()()(',
        '312(4324)324',
    ],
    email => [ # 0 .. 255
        'nrg@nrg.name',
        'asdsadad@nrg.name',
        'sadasd.adasd.asfsf@nrg.name',
        'asdasd-fghh@nrg.name',
        'ad@fdsf.fsdf.dsf.nrg.name'
    ],
    country => [ # two characters only
        'RU',
        #'TEST',
        'US',
        'UG'
    ],
};



my $plesk_url = 'https://192.168.1.1:8443/enterprise/control/agent.php';

our %plesk_valid_params = (
    api_version   => '1.6.3.0',
    username      => 'admin',
    password      => 'qwerty',
    url           => $plesk_url,
    debug         => 0,
);


my $manual_created_template_name = $ENV{'template_name'} || 'name';
my $manual_created_template_id = $ENV{'template_id'} || 1;


sub iterate {
    my $hash = shift;
    my $plesk_client = shift;

    my $result_hash = { };

    # initial data
    for (keys %$hash) {
        $result_hash->{$_} = $hash->{$_}->[0];
    }

    check_input_data($result_hash, $plesk_client);

    foreach my $key (keys %$hash) {
        my $old_value = $result_hash->{$key}; # rewrite 

        for my $index (1 .. scalar @{$hash->{$key}} - 1){
            $result_hash->{$key} = $hash->{$key}->[$index];
            check_input_data($result_hash, $plesk_client);
            # warn Dumper $result_hash;
        }
        $result_hash->{$key} = $old_value;
    }

    return $result_hash;
}


sub check_input_data {
    my $params = shift;
    my $plesk_client = shift;

    my $general_info = {
        pname   => $params->{pname},  # utf8 string 
        login   => $params->{login},
        passwd  => $params->{passwd},
        phone   => $params->{phone},
        email   => $params->{email},
        country => $params->{country}, # optional field
    };
    

    my $result_add_by_id = $plesk_client->Accounts->create( 
        general_info    => $general_info,
        'template-name' => $manual_created_template_name 
    );

    delete_all_accounts($plesk_client);
    # warn Dumper($general_info) . "\n" . $result_add_by_id->get_error_string unless
    unless (like( 
        $result_add_by_id->get_id,
        qr/^\d+$/,
        'Create account input data check'
    )) {
        warn $result_add_by_id->get_error_string . "\n" .
             Dumper $general_info;
    }
}


sub gen_passwd {
    my $passwd='';
    for (my $i=0; $i<8; $i++) {
        $passwd .= chr(rand( 0x3E ));
    }
    $passwd =~ tr/\x00-\x3D/A-Za-z0-9/;
    return $passwd;
}


# Delete all accounts
# STATIC(plesk_client)
sub delete_all_accounts {
    my $client = shift;
    
    is_deeply( 
      $client->Accounts->delete( all => 1)->is_success,
      1,
      'Delete all accounts'
    );
}


# Sub for create accounts online test
sub create_work_logins {
    my $plesk_client = shift;
    my $data_accumulator_for_online_tests = shift;

    my $result_add_by_id = $plesk_client->Accounts->create( 
        general_info    => create_unique_user_data('crby_login'),
        'template-name' => $manual_created_template_name 
    );

    like(
        $data_accumulator_for_online_tests->{user_id_from_create_with_tmpl_name} = 
        $result_add_by_id->get_id,

        qr/^\d+$/,
        'Create account with template name'
    );

    like(
        $data_accumulator_for_online_tests->{user_id_from_create_with_tmpl_id} = 
            
        $plesk_client->Accounts->create( 
            general_info  => create_unique_user_data('createby_id'),
            'template-id' => $manual_created_template_id
        )->get_id,

        qr/^\d+$/,
        'Create account with template id'
    );
}


sub create_unique_user_data {
    my $unique_id = shift;
    my %result_user_data = %{$test_user_data};

    $result_user_data{'pname'} = $result_user_data{'login'} = "testuser_$unique_id";
    return \%result_user_data;
}


# "Compare" two hashrefs
# Sub for get info online tests
sub _compare {
    my $checked = shift;
    my $template = shift;

    return '' unless ref $checked eq 'HASH' &&  ref $template eq 'HASH';
    
    foreach my $key (keys %$template) {
        my $key_name_from;  # field name in response from Plesk
        my $key_name_to;    # field name  

        # in request to Plesk field named "passwd"
        # in response from Plesk -- it named "password" :(

        if ($key =~ /pass/) {

            $key_name_from  = 'password';
            $key_name_to    = 'passwd';

        } else {
            $key_name_to = $key_name_from = $key;
        }

        if ($checked->{$key_name_from}) {
            return '' unless $template->{$key_name_to} eq 
                             $checked->{$key_name_from};
        } else {
            return '';
        }
    }

    return 1;
}



# Light weight Exporter
sub import {
    no strict 'refs';
    my $called_from = caller;

    foreach my $package_sub (@EXPORT) {
        # importing our sub into caller`s namespace
        *{$called_from . '::' . $package_sub} = \&$package_sub;
    }
}


1;

t/compoment.t

use strict;
use warnings;

use Carp;
use Test::More;
use Data::Dumper;

use lib 't';
use TestData;

BEGIN {
    plan tests => 10;
    use_ok( 'API::Plesk::Component' );
}

my $c = API::Plesk::Component->new(
    plesk => API::Plesk->new(%TestData::plesk_valid_params)
);

eval {
    $c->check_required_params({ test => 123}, qw(test));
};
ok(!$@);

eval {
    $c->check_required_params({ test => 123, test2 => 123}, [qw(test ddd)]);
};
ok(!$@);

eval {
    $c->check_required_params({ test => 123}, qw(qqq));
};
like($@, qr/Required field qqq!/);

eval {
    $c->check_required_params({ test => 123}, [qw(qqq ff)]);
};
like($@, qr/Required any of this fields: qqq, ff!/);

is_deeply(
    $c->sort_params({key => 1, key2 => 2, key3 => 3, key4 => 4}, [qw(key3 key2)], 'key'),
    [
        {key3 => 3},
        {key  => 1},
    ]
);

eval {
    $c->check_hosting({
        hosting => {
            type => 'vrt_hst',
            ftp_login => 'ert',
            ftp_password => '123',
            ip_address => '12.34.56.78',
        }
    })
};
ok(!$@);

eval {
    $c->check_hosting({
        hosting => {
            type => 'vrt_hst',
            ftp_login => 'ert',
            ftp_password => '123',
        }
    })
};
ok(!$@);

eval {
    $c->check_hosting({
        hosting => {
            type => 'vrt_ht',
            ftp_login => 'ert',
            ftp_password => '123',
        }
    })
};
like($@, qr/Unknown hosting type!/); 

is_deeply(
    $c->prepare_filter({id => [qw(1 2 3)], name => 'id'}, sort_keys => [qw(id name)]),
    [
        { id => 1 },
        { id => 2 },
        { id => 3 },
        { name => 'id' },
    ],
    'prepare_filter'
);


t/customer.t

#!/usr/bin/perl

use strict;
use warnings;

use Carp;
use Test::More;
use Data::Dumper;

use lib 't';
use TestData;

BEGIN { 
    plan tests => 3;
    use_ok('API::Plesk::Customer'); 
}

my $api = API::Plesk->new( %TestData::plesk_valid_params );

my $customers = API::Plesk::Customer->new( plesk => $api );

isa_ok($customers, 'API::Plesk::Customer');

is_deeply(
    $customers->get(id => 1, bulk_send => 1),
    { 
        filter => {id => 1},
        dataset => [ {gen_info => ''}, {stat => ''} ]
    },
    'get'
);
    

t/database.t

#!/usr/bin/perl

use strict;
use warnings;

use Carp;
use Test::More;
use Data::Dumper;

use lib qw(lib t);
use TestData;

BEGIN { 
    plan tests => 6;
    use_ok('API::Plesk::Database'); 
}

my $api = API::Plesk->new( %TestData::plesk_valid_params );

isa_ok($api->database, 'API::Plesk::Database');

is_deeply(
    $api->database->add_db(
        'webspace-id' => 1,
        name => 'test_db',
        type => 'MySQL',
        bulk_send => 1
    ),
    { 
        'webspace-id' => 1,
        name => 'test_db',
        type => 'MySQL',
    },
    'add_db'
);

is_deeply(
    $api->database->del_db(
        name => 'test_db',
        bulk_send => 1
    ),
    { 
        filter => {name => 'test_db'},
    },
    'del_db'
);
is_deeply(
    $api->database->add_db_user(
        'db-id'   => 1,
        login     => 'test_db_user',
        password  => '12345',
        bulk_send => 1
    ),
    [ 
        {'db-id'   => 1},
        {login     => 'test_db_user'},
        {password  => '12345'},
    ],
    'add_db_user'
);
 is_deeply(
    $api->database->del_db_user(
        'db-id' => 1,
        bulk_send => 1
    ),
    {
        filter => {'db-id' => 1} 
    },
    'del_db_user'
);
 

t/dns.t

#!/usr/bin/perl

use strict;
use warnings;

use Carp;
use Test::More;
use Data::Dumper;

use lib 't';
use TestData;

BEGIN { 
    plan tests => 2;
    use_ok('API::Plesk::DNS'); 
}

my $api = API::Plesk->new( %TestData::plesk_valid_params );

isa_ok($api->dns, 'API::Plesk::DNS');

t/ftp_user.t

#!/usr/bin/perl

use strict;
use warnings;

use Carp;
use Test::More;
use Data::Dumper;

use lib 't';
use TestData;

BEGIN { 
    plan tests => 2;
    use_ok('API::Plesk::FTPUser'); 
}

my $api = API::Plesk->new( %TestData::plesk_valid_params );

isa_ok($api->ftp_user, 'API::Plesk::FTPUser');

 view all matches for this distribution
 view release on metacpan -  search on metacpan

( run in 2.388 seconds using v1.00-cache-1.14-grep-d5ab23a-cpan-7bca77655a2 )