#!/usr/bin/perl
# ----------------------------------------------------------------------------
# This software is Copyright (C) 2007, All Rights Reserved
# ----------------------------------------------------------------------------
# This program is protected by local and international copyright laws. Any
# use of this program is subject to the the terms of the license agreement
# included as part of this distribution archive. Any other uses are strictly
# prohibited without the written permission of the Vendor and all
# other rights are reserved.
# -----------------------------------------------------------------------------
# Warning : Modifying this program violates your license agreement!
# -----------------------------------------------------------------------------

package IT::ArtMan::CustomMenus::Article;

# load modules
use strict;

use IT::Globals;

# ------------------------------------------------------------------------------
# Function    : preview
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::preview();
# ------------------------------------------------------------------------------

sub preview {
  my $menuName = shift || die "no menuName defined!";
  my $attr     = &IT::App::Menu::getMenuAttributes($menuName, 'preview');

  # error checking
  if (!$FORM->{'categoryNum'}) { die "No category was selected!\n"; }
  if (!$FORM->{'status'})      { die "No article status was selected!\n"; }

  # load publishRules
  &IT::FileLock::getLock('shared');
  my $publishRulesArray = &IT::DB::select({ 'tableName'  => $TABLE_PREFIX . 'publishrules' });
  &IT::FileLock::releaseLock();

  # get preview publishRule
  my $previewRule;
  foreach my $thisRule (@$publishRulesArray) {
    my $isPreviewRule = ($thisRule->{'type'} eq 'articlePages' && $thisRule->{'articlePagesUseRuleForPreviews'});
    if ($isPreviewRule) {
      $previewRule = $thisRule;
      last;
    }
  }
  if (!$previewRule) { die "There is no publish rule setup for article previews!\n"; }

  # get html
  my $articleRecord   = &IT::App::getRecordFieldsFromFormInput($attr->{'tableNameWithPrefix'});
  my $htmlWithSSIs    = &IT::ArtMan::CustomMenus::Publish::ArticlePages::getHtmlForArticle($previewRule, $articleRecord);

  # resolve ssi's
  my $html = &IT::SSIResolver::resolveSSIs($htmlWithSSIs);

  # Display page
  print &IT::Common::contentType('text/html');
  print $html;
  exit;
}



# ------------------------------------------------------------------------------
# Function    : updateLastUpdatedTime
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::updateLastUpdatedTime();
# ------------------------------------------------------------------------------

sub updateLastUpdatedTime {

  # update setting
  $SETTINGS->{'lastUpdated'} = &IT::App::Common::getTimeAdjustedToMatchGMT();

  # update settings.* placeholders
  &IT::App::defineGlobalTemplatePlaceholders();

  # save setting
  &IT::App::Menu::Admin::saveAdminSettingsToFile($SETTINGS);

}

# ------------------------------------------------------------------------------
# Function    : setNeedsPublishForArticleListsOnCategory
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::setNeedsPublishForArticleListsOnCategory($attr);
# ------------------------------------------------------------------------------

sub setNeedsPublishForArticleListsOnCategory {
  my $attr = shift;
  my $articleTable  = $TABLE_PREFIX . "article";
  my $categoryTable = $TABLE_PREFIX . "category";


  # get list of category nums
  my @categoryNums;
  my $categoryFieldnames = &IT::ArtMan::CustomFields::ArticleEditor::PrimaryCategory::getArticleCategoryFieldnames('all');
  if (exists $attr->{'originalRecord'} && $attr->{'originalRecord'}{'status'} eq 'visible') {       # from save (old record)
    foreach my $categoryField (@$categoryFieldnames) {
      push @categoryNums, $attr->{'originalRecord'}{ $categoryField };
    }
  }
  if (exists $attr->{'newOrUpdatedFields'} && $attr->{'newOrUpdatedFields'}{'status'} eq 'visible') {   # from save (new fields)
    foreach my $categoryField (@$categoryFieldnames) {
      push @categoryNums, $attr->{'newOrUpdatedFields'}{ $categoryField };
    }
  }
  if (exists $attr->{'selectedRecords'}) {      # from erase (selected records)
    foreach my $article (@{$attr->{'selectedRecords'}}) {
      unless ($article->{'status'} eq 'visible') { next; }
      foreach my $categoryField (@$categoryFieldnames) {
        push @categoryNums, $article->{ $categoryField };
      }
    }
  }
  @categoryNums = grep { $_ } @categoryNums;  # remove blank and "0" categoryNum values

  # return if no categories
  unless (@categoryNums) { return; }

  # update needsPublishForArticleLists for effected categories
  my $categoryNumsAsCSV = join ",", @categoryNums;
  my $whereQuery   = "num IN ($categoryNumsAsCSV)";
  my $updateHash   = { 'needsPublishForArticleLists' => '1' };
  &IT::FileLock::getLock('exclusive');
  &IT::DB::update($categoryTable, $whereQuery, $updateHash);
  &IT::FileLock::releaseLock();

}


# ------------------------------------------------------------------------------
# Function    : list_resetSearchQuery
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::list_resetSearchQuery($attr);
# ------------------------------------------------------------------------------

sub list_resetSearchQuery {
  my $attr     = shift || die "no menu attributes defined!";

  # set attribute values
  $attr->{'_categoryNum'} = '';
  $attr->{'_status'}      = '';

}

# ------------------------------------------------------------------------------
# Function    : list_getQueryValues
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::list_getQueryValues($attr);
# ------------------------------------------------------------------------------

sub list_getQueryValues {
  my $attr     = shift || die "no menu attributes defined!";

  $attr->{'_advancedSearch'} = &firstDefinedValue($attr->{'_advancedSearch'}, $FORM->{'_advancedSearch'}, &IT::App::Menu::List::loadLastQueryValueFromCookie($attr->{'menuName'}, 'advancedSearch'), 0);
  $attr->{'_categoryNum'}    = &firstDefinedValue($attr->{'_categoryNum'},    $FORM->{'_categoryNum'},    &IT::App::Menu::List::loadLastQueryValueFromCookie($attr->{'menuName'}, 'categoryNum'));
  $attr->{'_status'}         = &firstDefinedValue($attr->{'_status'},         $FORM->{'_status'},         &IT::App::Menu::List::loadLastQueryValueFromCookie($attr->{'menuName'}, 'status'));

  # blank out advanced search fields when not using advanced search
  unless ($attr->{'_advancedSearch'}) {
    $attr->{'_categoryNum'} = '';
    $attr->{'_status'} = '';
  }

}

# ------------------------------------------------------------------------------
# Function    : list_setQueryPlaceholders
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::list_setQueryPlaceholders($attr);
# ------------------------------------------------------------------------------

sub list_setQueryPlaceholders {
  my $attr     = shift || die "no menu attributes defined!";

  &IT::Template::setGlobalPlaceholder('_advancedSearch', $attr->{'_advancedSearch'});
  &IT::Template::setGlobalPlaceholder('_categoryNum', $attr->{'_categoryNum'});
  &IT::Template::setGlobalPlaceholder('_status', $attr->{'_status'});
  &_list_createCategoryOptionsPlaceholder($attr);

  my $isSearchResults = ($attr->{'keyword'} ne '' || $attr->{'_categoryNum'} || $attr->{'_status'}) ? 1 : 0;
  &IT::Template::setGlobalPlaceholder('_isSearchResults', $isSearchResults);

}

# ------------------------------------------------------------------------------
# Function    : _list_createCategoryOptionsPlaceholder
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::_list_createCategoryOptionsPlaceholder($attr);
# ------------------------------------------------------------------------------

sub _list_createCategoryOptionsPlaceholder {
  my $attr     = shift || die "no menu attributes defined!";
  my $template = "admin/menus/article.html";

  # get category numbers
  my $categoryRecords = &IT::ArtMan::CustomMenus::Category::getSortedFilteredCategoryArray({
                          limitByCategoryAccessRights => 1,
                        });

  # generate category options
  my $selectedCategoryNum   = $attr->{'_categoryNum'};
  my $categoryOptions;
  foreach my $category (@$categoryRecords) {
    my $num = $category->{'num'};
    my $isSelected       = ($num == $selectedCategoryNum);
    my $cellPlaceholders = {};
    $cellPlaceholders->{'_breadcrumbText'} = &IT::ArtMan::CustomMenus::Category::generateTextBreadcrumb($num);
    $cellPlaceholders->{'_optionValue'}    = $num;
    $cellPlaceholders->{'_selected'}       = $isSelected ? ' selected="selected"'       : '';

    $categoryOptions .= &templateCell($template, 'listPageCategoryOption', $cellPlaceholders);
  }

  # create category options placeholder
  &IT::Template::setGlobalPlaceholder('listPageCategoryOptions', $categoryOptions);

}

# ------------------------------------------------------------------------------
# Function    : createDbWhereQuery
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::createDbWhereQuery();
# ------------------------------------------------------------------------------

sub createDbWhereQuery {
  my $dbWhereQuery;
  my $isAdmin  = ($CURRENT_USER->{'accessLevel'} >= 9);
  my $isEditor = ($CURRENT_USER->{'accessLevel'} >= 4);

  # limit by ownership
  unless ($isAdmin || $isEditor) {
    $dbWhereQuery .= "createdByUserNum = $CURRENT_USER->{'num'}";
  }

  # limit by category access
  my $allowedCategoryNumbers = &_getHashOfCategoryNumbersUserHasAccessTo();
  unless ($allowedCategoryNumbers->{'all'}) {
    my $categoryFieldnames = &IT::ArtMan::CustomFields::ArticleEditor::PrimaryCategory::getArticleCategoryFieldnames('all');
    my $categoryNumsAsCSV  = join ',', (keys %$allowedCategoryNumbers, 0, "''");  # include 0 and '' so undefined article category fields will match as well
    $dbWhereQuery         .= " AND " if $dbWhereQuery;
    $dbWhereQuery         .= "(";
    $dbWhereQuery         .= join " AND ", map { "$_ IN ($categoryNumsAsCSV)"  } @$categoryFieldnames;
    $dbWhereQuery         .= ")";
  }

  return $dbWhereQuery;
}

# ------------------------------------------------------------------------------
# Function    : _getHashOfCategoryNumbersUserHasAccessTo
# Description : Note, if a category number of "all" exists the user has access
#               to all categories.  Either a single hash value of "all" or a list
#               of valid category nums will be returned.
# Usage       : my $allowedCategoryNumbers = &_getHashOfCategoryNumbersUserHasAccessTo();
# ------------------------------------------------------------------------------

sub _getHashOfCategoryNumbersUserHasAccessTo {
  my %allowedCategoryNumbers;

  ### get allowed categories
  my($accessRights, $emailRights) = &IT::ArtMan::CustomFields::AccountManager::CategoryRights::getCategoryRightsHashesFromRecordValue($CURRENT_USER->{'categoryRights'});
  my $isAdmin  = ($CURRENT_USER->{'accessLevel'} >= 9);
  if ($isAdmin || $accessRights->{'all'}) {              # Add "All category" access
    $allowedCategoryNumbers{'all'} = 1;
  }
  else {                                                 # Add each category number
    foreach my $categoryNum (keys %$accessRights) {
      $allowedCategoryNumbers{ $categoryNum } = 1;
    }
  }

  #
  return \%allowedCategoryNumbers;
}

# ------------------------------------------------------------------------------
# Function    : list_createUserSearchDbQuery
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::list_createUserSearchDbQuery($attr);
# ------------------------------------------------------------------------------

sub list_createUserSearchDbQuery {
  my $attr = shift || die "no menu attributes defined!";

  # add search keyword query
  $attr->{'userSearchDbQuery'} = &IT::App::Menu::List::createUserSearchDbQueryFromKeyword($attr);

  # add category query (if defined)
  if ($attr->{'_categoryNum'}) {
    my $safeCategoryNum           = int $attr->{'_categoryNum'};
    my $categoryFieldnames        = &IT::ArtMan::CustomFields::ArticleEditor::PrimaryCategory::getArticleCategoryFieldnames('all');
    $attr->{'userSearchDbQuery'} .= " AND " if $attr->{'userSearchDbQuery'};
    $attr->{'userSearchDbQuery'} .= "(";
    $attr->{'userSearchDbQuery'} .= join " OR ", map { "$_ = '$safeCategoryNum'"  } @$categoryFieldnames;
    $attr->{'userSearchDbQuery'} .= ")";
  }

  # add status query (if defined)
  if ($attr->{'_status'}) {
    my $escapedStatus = &IT::DB::getEscapedValue( $attr->{'_status'} );
    $attr->{'userSearchDbQuery'} .= " AND status = '$escapedStatus'";
  }

}

# ------------------------------------------------------------------------------
# Function    : list_saveLastQueryToCookie
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::list_saveLastQueryToCookie($attr);
# ------------------------------------------------------------------------------

sub list_saveLastQueryToCookie {
  my $attr = shift || die "no menu attributes defined!";

    &IT::App::Menu::List::saveLastQueryToCookie({
      menuName       => $attr->{'menuName'},
      keyword        => $attr->{'keyword'},
      perPage        => $attr->{'recordsPerPage'},
      pageNum        => $attr->{'pageNumber'},
      advancedSearch => $attr->{'_advancedSearch'},
      categoryNum    => $attr->{'_categoryNum'},
      status         => $attr->{'_status'},
    });

}




# ------------------------------------------------------------------------------
# Function    : list_loadCategoryPlaceholders
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::list_loadCategoryPlaceholders($attr, $record);
# ------------------------------------------------------------------------------

sub list_loadCategoryPlaceholders {
  my $attr          = shift || die "no attr specified!";
  my $articleRecord = shift;

  # load category record
  my $categoryTableName  = $TABLE_PREFIX . "category";
  my $categoryRecords    = &IT::ArtMan::CustomMenus::Category::getHashOfCategoryRecords();
  my $categoryRecord     = $categoryRecords->{ $articleRecord->{'categoryNum'} };

  # get category placeholders
  my $categoryPlaceholders;
  if ($categoryRecord) {
    $categoryPlaceholders = &IT::App::getPlaceholdersFromRecordFields($categoryTableName, $categoryRecord);
  }

  # if no category specified, defined blank placeholders
  else {
    my @unsortedFieldNames = &IT::DB::Schema::getFieldNames($categoryTableName);
    foreach my $fieldName (@unsortedFieldNames) {
      $categoryPlaceholders->{$fieldName} = '';
    }
  }

  # get category nums
  my @categoryNums;
  my $categoryFieldnames = &IT::ArtMan::CustomFields::ArticleEditor::PrimaryCategory::getArticleCategoryFieldnames('all');
  foreach my $categoryFieldname (@$categoryFieldnames) {
    my $categoryNum = $articleRecord->{ $categoryFieldname };
    if ($categoryNum) { push @categoryNums, $categoryNum; }
  }

  # create breadcrumb placeholder
  my @breadcrumbs;
  foreach my $categoryNum (@categoryNums) {
    push @breadcrumbs, &IT::ArtMan::CustomMenus::Category::generateTextBreadcrumb( $categoryNum );
  }
  my $breadcrumbsAsTSV = join "\t", @breadcrumbs;
  $categoryPlaceholders->{'breadcrumb'}  = $breadcrumbs[0];
  $categoryPlaceholders->{'breadcrumbs'} = &IT::Template::createListPlaceholder($breadcrumbsAsTSV);

  # prefix with category. and add to cellPlaceholders
  $attr->{'cellPlaceholders'} = {};
  foreach my $name (keys %$categoryPlaceholders) {
    $attr->{'cellPlaceholders'}{"category.$name"} = $categoryPlaceholders->{$name};
  }

}

# ------------------------------------------------------------------------------
# Function    : setNeedsPublishFieldValue
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::setNeedsPublishFieldValue($attr);
# ------------------------------------------------------------------------------

sub setNeedsPublishFieldValue {
  my $attr = shift;

  $attr->{'newOrUpdatedFields'}{'needsPublish'}   = 1;
}


# ------------------------------------------------------------------------------
# Function    :
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::edit_errorChecking($attr);
# ------------------------------------------------------------------------------

sub edit_errorChecking {
  my $attr     = shift || die "no attr specified!";
  my $record   = $attr->{'recordFields'};

  # error checking
  my @errors;
  push @errors, &_enforceArticleAccessRights($record);
  push @errors, &_enforceCategoryAccessRights($record);
  # return errors;
  my $errors = join "\n", @errors;
  if ($errors) {
    &IT::App::Menu::list($attr->{'menuName'}, $errors);
  }

}

# ------------------------------------------------------------------------------
# Function    :
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::save_errorChecking($attr);
# ------------------------------------------------------------------------------

sub save_errorChecking {
  my $attr     = shift || die "no attr specified!";
  my $record   = $attr->{'recordFields'};

  # error checking
  my @errors;
  push @errors, &_enforceArticleAccessRights($record) unless $attr->{'isNewRecord'};
  push @errors, &_enforceCategoryAccessRights($record);

  # return errors;
  my $errors = join "\n", @errors;
  if ($errors) {
    if    ($FORM->{'_addOrEdit'} eq 'add')  { &IT::App::Menu::add($attr->{'menuName'}, $errors); }
    elsif ($FORM->{'_addOrEdit'} eq 'edit') { &IT::App::Menu::edit($attr->{'menuName'}, $errors); }
    else                                    { die "hidden field _addOrEdit not set to either 'add' or 'edit'!"; }
  }

}

# ------------------------------------------------------------------------------
# Function    : displayErrorOnFilenameCollisions
# Description : call on preSave
# Usage       : &IT::ArtMan::CustomMenus::Article::displayErrorOnFilenameCollisions($attr);
# ------------------------------------------------------------------------------

sub displayErrorOnFilenameCollisions {
  my $attr     = shift || die "no attr specified!";
  my @errors;

  # get record data
  my %record;
  my $isCreatingRecord   = $attr->{'isNewRecord'};
  my $isModifyingRecord  = !$isCreatingRecord;
  if ($isCreatingRecord)  { %record = %{$attr->{'newOrUpdatedFields'}}; }
  if ($isModifyingRecord) { %record = ( %{$attr->{'originalRecord'}}, %{$attr->{'newOrUpdatedFields'}} ); }

  # check for filename collisions
  my $fileNameHasChanged = ($attr->{'newOrUpdatedFields'}{'filename'} ne $attr->{'originalRecord'}{'filename'});  # note: changing category could change filepath without setting this to true
  my $checkForCollisions = $isCreatingRecord || ($isModifyingRecord && $fileNameHasChanged);

  if ($checkForCollisions) {
    my $existingFilepath = &IT::ArtMan::CustomMenus::Publish::isExistingFilesWithConflictingNamesFor('article', \%record);
    if ($existingFilepath) {
      push @errors, "Please select another filename, an existing file has the same name that this article will be published as.";
      push @errors, "Existing file: $existingFilepath";
    }
  }

  # return errors;
  my $errors = join "\n", @errors;
  if ($errors) {
    if    ($FORM->{'_addOrEdit'} eq 'add')  { &IT::App::Menu::add($attr->{'menuName'}, $errors); }
    elsif ($FORM->{'_addOrEdit'} eq 'edit') { &IT::App::Menu::edit($attr->{'menuName'}, $errors); }
    else                                    { die "hidden field _addOrEdit not set to either 'add' or 'edit'!"; }
  }

}

# ------------------------------------------------------------------------------
# Function    :
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Article::eraseErrorChecking($attr);
# ------------------------------------------------------------------------------

sub eraseErrorChecking {
  my $attr     = shift || die "no attr specified!";

  # error checking
  my @errors;
  foreach my $record (@{$attr->{'selectedRecords'}}) {
    push @errors, &_enforceArticleAccessRights($record);
    push @errors, &_enforceCategoryAccessRights($record);
  }


  # return errors;
  my $errors = join "\n", @errors;
  if ($errors) {
    &IT::App::Menu::list($attr->{'menuName'}, $errors);
  }

}

# ------------------------------------------------------------------------------
# Function    : _enforceArticleAccessRights
# Description :
# Usage       : push @errors, &_enforceArticleAccessRights($createdByUserNum);
# ------------------------------------------------------------------------------

sub _enforceArticleAccessRights {
  my $record = shift || die "no record specified!";

  # allow creating new articles
  if (!$record->{'num'}) { return; }

  my $isEditorOrAdmin   = ($CURRENT_USER->{'accessLevel'} >= 4);
  my $isTrustedOwner    = ($CURRENT_USER->{'accessLevel'} >= 3 && $record->{'createdByUserNum'} == $CURRENT_USER->{'num'});

  my @errors;
  unless ($isEditorOrAdmin || $isTrustedOwner) {
    push @errors, "You are not allowed to access the specified article (Article #$record->{'num'})!";
  }

  return @errors;
}

# ------------------------------------------------------------------------------
# Function    : _enforceCategoryAccessRights
# Description : prevent access to articles in categories user doesn't have access to
# Usage       : &_enforceCategoryAccessRights($attr, $fields);
# ------------------------------------------------------------------------------

sub _enforceCategoryAccessRights {
  my $record = shift;
  my @errors;

  # limit category access
  my $isAdmin                = ($CURRENT_USER->{'accessLevel'} >= 9);
  my $allowedCategoryNumbers = &_getHashOfCategoryNumbersUserHasAccessTo();
  unless ($isAdmin || $allowedCategoryNumbers->{'all'}) {

    my $categoryFieldnames = &IT::ArtMan::CustomFields::ArticleEditor::PrimaryCategory::getArticleCategoryFieldnames('all');
    foreach my $categoryFieldname (@$categoryFieldnames) {
      my $categoryNum = $record->{$categoryFieldname};

      if ($categoryNum && !$allowedCategoryNumbers->{$categoryNum}) {
        my $isSaving = ($FORM->{'action'} eq 'articleSave' || $FORM->{'_defaultAction'} eq 'articleSave' );
        if ($isSaving) { push @errors, "You can't save articles to categories you don't have access to!"; last; }
        else           { push @errors, "Article is in a category you don't have access to!"; last; }
      }
    }
  }

  return @errors;
}

# ----------------------------------------------------------------------------
# end of file
1;

