CatalystX-CMS

 view release on metacpan or  search on metacpan

lib/CatalystX/CMS/File.pm  view on Meta::CPAN

package CatalystX::CMS::File;
use strict;
use warnings;
use base qw(
    SVN::Class::File
    Path::Class::File::Lockable
    Class::Accessor::Fast
);
use MRO::Compat;
use mro 'c3';
use Carp;
use Data::Dump qw( dump );

our $VERSION = '0.011';

__PACKAGE__->mk_accessors(qw( attrs content ext has_unsaved_changes ));

=head1 NAME

CatalystX::CMS::File - template file class

=head1 SYNOPSIS

 use CatalystX::CMS::File;

 # longhand
 my $file = CatalystX::CMS::File->new( $path_to_file );
 $file->fetch if $page->size;
 $file->content('hello world');
 $file->title('super foo');
 $file->write;
 $file->commit or die $file->errstr;
 
 # shorthand
 my $file = CatalystX::CMS::File->new(
                        path    => $path_to_file,
                        title   => 'super foo',
                        content => 'hello world',
                        );
 eval { $file->save  } or die $file->errstr;
 print "$file was saved\n";
                        
=head1 DESCRIPTION

CatalystX::CMS::File is the object model for the Template Toolkit files that
make up the content system of your application. 
Each object represents a C<.tt>
file on the filesystem in a Subversion working directory.
An object can be read, written, locked and unlocked on the filesystem.
As a subclass of SVN::Class::File, the object can be interrogated 
for its history, added, deleted, committed or updated.

=head2 new( I<path> )

=head2 new( path => I<path> [, %param ] )

Create a new object.

Either form of new() may be used, but I<path> is always
required.

=cut

sub new {
    my $class = shift;
    my @param = @_;
    my ( $self, %arg );

    if ( !@param ) {
        croak "path required";
    }
    elsif ( @param == 1 ) {
        $self = $class->next::method(@param);
    }
    elsif ( @param % 2 ) {
        croak "new() requires either a path or a hash of key/value pairs";
    }
    elsif ( @param == 2 && $param[0] ne 'path' ) {
        $self = $class->next::method(@param);
    }
    else {
        %arg = @param;
        my $path = delete $arg{path} or croak "path required";
        $self = $class->next::method($path);
    }

    # add other attributes
    $self->{$_} = $arg{$_} for keys %arg;

    $self->{attrs} ||= {};

    # path cleanup
    $self->{ext} ||= '.tt';

    # re-create the object with extension
    my $ext = $self->{ext};
    unless ( $self =~ m/$ext$/ ) {
        $self = $self->new( $self . $self->{ext} );
    }

    #carp dump $self;

    return $self;
}

=head2 read

Reads the file from disk and parses the metadata attributes and content,
thereafter accessible via the attrs() and content() methods.

lib/CatalystX/CMS/File.pm  view on Meta::CPAN

        return 1;
    }
    elsif ( $stat == 0
        && !$self->error )
    {
        return 1;
    }

    return 0;
}

sub _parse_page {
    my ( $self, $buf ) = @_;

    # example:
    #
    #  [% # CMS
    #       cmspage.attrs.title = 'foo'
    #  %]
    #
    #   this is a page.
    #

    $self->_escape( \$buf );

    my ( $attrs, $content )
        = ( $buf =~ m/^\s*\[\%\s+\#\s*CMS\s+(.+?)\%\]\s*(.*)$/s );
    if ( $attrs && $content ) {
        my $depth = 100;
        my $count = 0;
        while ( $attrs =~ m/\bcmspage\.attrs\.(\w+)\s*=\s*(['"])(.+?)\2/sg ) {

            last if $count++ > $depth;

            my $key = $1;
            my $val = $3;

            # reserved words
            next if $key eq 'attrs';
            next if $key eq 'content';
            next if $key eq 'page';

            $self->{attrs}->{$key} = $val;
        }

        $self->content($content);
    }
    else {
        $self->content($buf);
    }
    return $self;
}

=head2 write( [I<ignore_lock>] )

Writes attrs() and content() to file location in TT-compatible format.

Will croak on any error.

If the I<ignore_lock> flag is true, write() will ignore any true
value of locked(). Otherwise, will croak() if locked() is true.

Returns the size of the resulting file.

=cut

sub write {
    my $self = shift;
    my $force = shift || 0;

    if ( $self->locked && !$force ) {
        croak "write failed. $self is locked";
    }

    my $fh = $self->openw();

    # make sure we have at least one newline at end of file.
    my $content = $self->content || '';
    chomp $content;
    $content .= "\n";
    $self->_unescape( \$content );

    print {$fh} join( "\n", $self->_ttify_attrs, $content )
        or croak "write failed for $self: $!";
    $fh->close;

    return -s $self;
}

my $COMMENT      = 'CXCMS COMMENT: DO NOT REMOVE';
my @special_tags = qw( textarea );

sub _escape {
    my ( $self, $buf_ref ) = @_;

    for my $tag (@special_tags) {
        if ( $$buf_ref =~ m!</?$tag!i ) {
            $$buf_ref =~ s,<(/?${tag}.*?)>,<!-- $COMMENT $1 -->,sgi;
        }
    }
}

sub _unescape {
    my ( $self, $buf_ref ) = @_;

    if ( $$buf_ref =~ m!$COMMENT!i ) {
        $$buf_ref =~ s,<!-- $COMMENT (.+?) -->,<$1>,sgi;
    }
}

=head2 create( I<user> )

Acquires lock for I<user> and writes files as a new page.

=cut

sub create {
    my $self = shift;
    my $user = shift or croak "user required";

    # create any required parent directories
    $self->dir->mkpath();

    if ( $self->locked or -s $self ) {
        croak "cannot create $self : locked or already exists";
    }

    $self->lock($user);
    $self->attrs->{owner} = $user;
    $self->content('[ this is a new page ]');
    return $self->write(1);
}

=head2 update

Calls write().

B<NOTE:> This is not the same as the SVN::Class->update method!
If you want that method, use the up() alias instead.

=cut

sub update {
    my $self = shift;
    if ( !-s $self ) {
        croak "cannot update an empty file";
    }
    $self->write(@_);
}

sub _ttify_attrs {
    my $self = shift;

    # make a TT-hash out of a Perl hash
    my $attrs = $self->attrs;
    my $buf   = "[% # CMS\n\n";
    for my $key ( sort keys %$attrs ) {
        $buf .= "    cmspage.attrs.$key = '" . qq($attrs->{$key}) . "';\n";
    }
    return $buf . "\n%]";
}

=head2 save( I<message> [, I<leave_lock>, I<username> ] )

Will write() file, add() to the SVN workspace if necessary,
and then call commit( I<message> ).

Returns -1 if status() of file indicates no modification.
Otherwise, returns commit() return value.

Pass a true for I<leave_lock> to leave the lock file intact
after the commit().

Pass an authorized I<username> to have the commit() made with that
credential.

=cut

sub save {
    my $self       = shift;
    my $message    = shift || '[no log message]';
    my $leave_lock = shift || 0;
    my $username   = shift;

    # pass force flag to write() since we want lock



( run in 2.530 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )