#!/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::Admin::PublishRules;

# load modules
use strict;
use warnings;
no warnings qw/uninitialized numeric/; # turn off unneeded warnings

use IT::Globals;

use vars qw($ARTICLE_FIELD_LIST);
use vars qw($CACHED_PUBLISH_RULES $CACHED_PUBLISH_RULE_CATEGORY_NUMS);

# ------------------------------------------------------------------------------
# Function    : clearAllCachedData
# Description : Clear all cached data so that any changed made to cached data
#               on disk will be reloaded.
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::clearAllCachedData();
# ------------------------------------------------------------------------------

sub clearAllCachedData {

  undef $CACHED_PUBLISH_RULES;

}

# ------------------------------------------------------------------------------
# Function    : getSortedArrayOfPublishRules
# Description : Return array ref of publish rules sorted by type.  Return cached
#               value if function has previously been called.
# Usage       : my $publishRulesArray = &IT::ArtMan::CustomMenus::Admin::PublishRules::getSortedArrayOfPublishRules();
# ------------------------------------------------------------------------------

sub getSortedArrayOfPublishRules {

  unless ($CACHED_PUBLISH_RULES) {

    # load publishRule records
    &IT::FileLock::getLock('shared');
    $CACHED_PUBLISH_RULES   = &IT::DB::select({ 'tableName'  => $TABLE_PREFIX . 'publishrules' });
    $CACHED_PUBLISH_RULES ||= [];
    &IT::FileLock::releaseLock();

    # set sort order
    my $typeSortOrder = {};
    $typeSortOrder->{'articlePages'} = 1; # publish articles first so article links aren't 404 during long publish
    $typeSortOrder->{'articleList'}  = 2; # publish this next so category menu doesn't link to unpublished article list
    $typeSortOrder->{'categoryMenu'} = 3;
    $typeSortOrder->{'singlePage'}   = 4;
    $typeSortOrder->{'externalUrl'}  = 5;

    # sort publish rules
    @$CACHED_PUBLISH_RULES = sort {
      $typeSortOrder->{$a->{'type'}} <=> $typeSortOrder->{$b->{'type'}}
    } @$CACHED_PUBLISH_RULES;
  }

  # return records
  return $CACHED_PUBLISH_RULES;

}

# ------------------------------------------------------------------------------
# Function    : getSelectedCategoryNumsForPublishRule
# Description : Returns an array of category nums based on the publish rules
#               category filter and whether includeChildCategories is specified or
#               not.  If category filter isn't used all category numbers are returned.
# Usage       : my $selectedCategoryNums = &IT::ArtMan::CustomMenus::Admin::PublishRules::getSelectedCategoryNumsForPublishRule($rule, $rule->{'includeChildCategories'});
# ------------------------------------------------------------------------------

sub getSelectedCategoryNumsForPublishRule {
  my $rule                   = shift || die "no rule specified!";
  my $includeChildCategories = (shift) ? 1 : 0;

  # Cache results if needed
  unless ($CACHED_PUBLISH_RULE_CATEGORY_NUMS->{ $rule->{'num'} }{ $includeChildCategories }) {

    # get all categories (except disabled ones which we don't publish)
    my $categoryRecords = &IT::ArtMan::CustomMenus::Category::getSortedCategoryArray();
    @$categoryRecords   = grep { $_->{'type'} ne 'disabled' } @$categoryRecords;  # remove disabled categories

    ### if no category filter... return all categories
    if (!$rule->{'useCategoryFilter'}) {
      my @selectedCategoryNums = map { $_->{'num'} } @$categoryRecords;
      $CACHED_PUBLISH_RULE_CATEGORY_NUMS->{ $rule->{'num'} }{ $includeChildCategories } = \@selectedCategoryNums;
    }

    ### if category filter... return selected categories (and their descendants if requested)
    if ($rule->{'useCategoryFilter'}) {

      # get selected categories from rule
      my %ruleCategoryNumbers = map { $_ => 1 } split /\t/, $rule->{'categoryFilterSelections'};
      my $including  = $rule->{'categoryFilterIncludeOrExclude'} eq 'include';
      my $excluding  = $rule->{'categoryFilterIncludeOrExclude'} eq 'exclude';

      my @selectedCategoryNums;
      foreach my $record (@$categoryRecords) {
        my $isSelected = $ruleCategoryNumbers{ $record->{'num'} };
        if (($including && $isSelected) || ($excluding && !$isSelected)) {
          push @selectedCategoryNums, $record->{'num'};
        }
      }

      # add child categories
      if ($includeChildCategories) {
        my $childCategoryNums = &IT::ArtMan::CustomMenus::Category::getChildCategoryNumsFor(@selectedCategoryNums);
        push @selectedCategoryNums, @$childCategoryNums;
      }

      # add to cache
      $CACHED_PUBLISH_RULE_CATEGORY_NUMS->{ $rule->{'num'} }{ $includeChildCategories } = \@selectedCategoryNums;
    }
  }

  #
  my $selectedCategoryNums = $CACHED_PUBLISH_RULE_CATEGORY_NUMS->{ $rule->{'num'} }{ $includeChildCategories } || [];
  return $selectedCategoryNums;
}


# ------------------------------------------------------------------------------
# Function    : getFilenameFormatAndType
# Description :
# Usage       : my($filenameFormat, $singleOrMultiFile) = &IT::ArtMan::CustomMenus::Admin::PublishRules::getFilenameFormatAndType($ruleOrFORM);
# ------------------------------------------------------------------------------

sub getFilenameFormatAndType {
  my $rule = shift;

  # get filename format
  my($filenameFormat, $singleOrMultiFile);
  if    ($rule->{'type'} eq 'singlePage')                                      { $singleOrMultiFile = "single"; $filenameFormat = "$rule->{'singlePageFileName'}.$rule->{'singlePageFileExt'}"; }
  elsif ($rule->{'type'} eq 'articlePages')                                    { $singleOrMultiFile = "multi";  $filenameFormat = "$rule->{'articlePagesFileName'}.$rule->{'articlePagesFileExt'}"; }
  elsif ($rule->{'type'} eq 'categoryMenu') {
    if    ($rule->{'categoryListPublishTopOrCategory'} eq 'top')               { $singleOrMultiFile = "single"; $filenameFormat = "$rule->{'categoryListTopMenuFileName'}.$rule->{'categoryListTopMenuFileExt'}"; }
    elsif ($rule->{'categoryListPublishTopOrCategory'} eq 'category')          { $singleOrMultiFile = "multi";  $filenameFormat = "$rule->{'categoryListMenuFileName'}.$rule->{'categoryListMenuFileExt'}"; }
    else                                                                       { die "unknown 'categoryListPublishTopOrCategory' value '$rule->{'categoryListPublishTopOrCategory'}'"; }
  }
  elsif ($rule->{'type'} eq 'articleList')  {
    if    ($rule->{'articleListPublishGlobalOrCategoryIndexes'} eq 'global')   { $singleOrMultiFile = "single"; $filenameFormat = "$rule->{'articleListGlobalFileName'}.$rule->{'articleListGlobalFileExt'}"; }
    elsif ($rule->{'articleListPublishGlobalOrCategoryIndexes'} eq 'category') { $singleOrMultiFile = "multi";  $filenameFormat = "$rule->{'articleListCategoryFileName'}.$rule->{'articleListCategoryFileExt'}"; }
    else                                                                       { die "unknown 'articleListPublishGlobalOrCategoryIndexes' value '$rule->{'articleListPublishGlobalOrCategoryIndexes'}'"; }
  }
  elsif ($rule->{'type'} eq 'externalUrl')  { return ''; }
  else { die "unknown rule type of '$rule->{'type'}' for '$rule->{'name'}'"; }

  if (wantarray) { return ($filenameFormat, $singleOrMultiFile); }
  else           { return $filenameFormat; }

}

# ------------------------------------------------------------------------------
# Function    : getPublishDirAndUrl
# Description :
# Usage       : my($publishDir, $publishUrl) = &IT::ArtMan::CustomMenus::Admin::PublishRules::getPublishDirAndUrl($ruleRecordHash);
# ------------------------------------------------------------------------------

sub getPublishDirAndUrl {
  my $rule = shift || die "no rule specified!";

  # error checking
  if (not exists $rule->{'useCustomPublishDir'}) { die "no 'useCustomPublishDir' value specified in rule record!"; }

  # get publish directory
  my ($publishDir, $publishUrl);
  if ($rule->{'useCustomPublishDir'}) {
    $publishDir = $rule->{'customPublishDir'} || die "no customPublishDir specified for publish rule!";
    $publishUrl = $rule->{'customPublishUrl'} || die "no customPublishUrl specified for publish rule!";
  }
  else {
    $publishDir = $SETTINGS->{'publish_dir'} || die "no publish_dir specified in admin settings!";
    $publishUrl = $SETTINGS->{'publish_url'} || die "no publish_url specified in admin settings!";
  };

  # return values
  return($publishDir, $publishUrl);

}

# ------------------------------------------------------------------------------
# Function    : defineSettingsPlaceholders
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::defineSettingsPlaceholders();
# ------------------------------------------------------------------------------

sub defineSettingsPlaceholders {
  my $attr = shift || die "no attr value defined!";

  # defined placeholders
  $attr->{'placeholders'}{'publish_dir'}           = &firstDefinedValue($FORM->{'publish_dir'}, $SETTINGS->{'publish_dir'});
  $attr->{'placeholders'}{'publish_url'}           = &firstDefinedValue($FORM->{'publish_url'}, $SETTINGS->{'publish_url'});
  $attr->{'placeholders'}{'publishFileExtension'}  = &firstDefinedValue($FORM->{'publishFileExtension'}, $SETTINGS->{'publishFileExtension'});
  $attr->{'placeholders'}{'displayTemplateIdTags'} = &firstDefinedValue($FORM->{'displayTemplateIdTags'}, $SETTINGS->{'displayTemplateIdTags'});

}

# ------------------------------------------------------------------------------
# Function    : savePublishSettings
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::savePublishSettings($menuName);
# ------------------------------------------------------------------------------

sub savePublishSettings {
  my $menuName = shift || die "no menuname specified!";

  &IT::App::demoModeAlertAndAction("Saving publish rules is disabled in demo mode.", sub { &IT::App::Menu::list('publishRules') });

  # error checking
  my $errors = &IT::App::Menu::Admin::getFieldValidationErrors();
  if ($errors) {
    &IT::App::alert($errors);
    &IT::App::Menu::list($menuName);
    exit;
  }

  # save settings
  $SETTINGS->{'publish_dir'}           = &firstDefinedValue($FORM->{'publish_dir'},           $SETTINGS->{'publish_dir'});
  $SETTINGS->{'publish_url'}           = &firstDefinedValue($FORM->{'publish_url'},           $SETTINGS->{'publish_url'});
  $SETTINGS->{'publishFileExtension'}  = &firstDefinedValue($FORM->{'publishFileExtension'},  $SETTINGS->{'publishFileExtension'});
  $SETTINGS->{'displayTemplateIdTags'} = &firstDefinedValue($FORM->{'displayTemplateIdTags'}, $SETTINGS->{'displayTemplateIdTags'});
  &IT::App::Menu::Admin::saveAdminSettingsToFile($SETTINGS);


  # display list page
  &IT::App::alert("Publish settings have been saved.");
  &IT::App::Menu::list($menuName);
  exit;

}

# ------------------------------------------------------------------------------
# Function    : publishSelected
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::publishSelected();
# ------------------------------------------------------------------------------

sub publishSelected {
  my $menuName = shift || die "no menuname specified!";

  # get selected rule numbers to publish
  my $selectedRuleNumbersHash  = &IT::App::Menu::getSelectedRecordNumbersAsHash();
  my $selectedRuleNumbersAsCSV = join ",", keys %$selectedRuleNumbersHash;

  # error checking
  if (!keys %$selectedRuleNumbersHash) {
    &IT::App::alert("No publish rules were selected!");
    &IT::App::Menu::list($menuName);
    exit;
  }

  # redirect to publish
  &IT::Common::redirectBrowserToURL("$CGIURL?action=publishAll&selectedRuleNumbers=$selectedRuleNumbersAsCSV&nextAction=publishRulesList");
  exit;
}

# ------------------------------------------------------------------------------
# Function    : createPublishedAsFilenamesPlaceholder
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::createPublishedAsFilenamesPlaceholder($attr, $record);
# ------------------------------------------------------------------------------

sub createPublishedAsFilenamesPlaceholder {
  my $attr   = shift;
  my $record = shift;

  # get publish dir and url
  my $publishDir = $record->{'useCustomPublishDir'} ? $record->{'customPublishDir'} : $SETTINGS->{'publish_dir'};
  my $publishUrl = $record->{'useCustomPublishDir'} ? $record->{'customPublishUrl'} : $SETTINGS->{'publish_url'};
  #my $truncatedPublishDir = $publishDir;
  #for (1..10) {
  #  if (length $truncatedPublishDir > 45) { $truncatedPublishDir =~ s|^.{6,}?([\\\/])|...$1|; }
  #}


  ### get list of published file formats
  my @publishFiles;
  if ($record->{'type'} eq 'categoryMenu') {
    my $topMenuFile = $record->{'categoryListTopMenuFileName'} . '.' . $record->{'categoryListTopMenuFileExt'};
    my $catMenuFile = $record->{'categoryListMenuFileName'}    . '.' . $record->{'categoryListMenuFileExt'};
    if    ($record->{'categoryListPublishTopOrCategory'} eq 'top')      { push(@publishFiles, $topMenuFile); }
    elsif ($record->{'categoryListPublishTopOrCategory'} eq 'category') { push(@publishFiles, $catMenuFile); }
    else { die "unrecognized value '$record->{'categoryListPublishTopOrCategory'}' for 'categoryListPublishTopOrCategory'!"; }
  }

  elsif  ($record->{'type'} eq 'articleList') {
    my $globalListFile = $record->{'articleListGlobalFileName'}   . '.' . $record->{'articleListGlobalFileExt'};
    my $catListFile    = $record->{'articleListCategoryFileName'} . '.' . $record->{'articleListCategoryFileExt'};
    if    ($record->{'articleListPublishGlobalOrCategoryIndexes'} eq 'global')   { push(@publishFiles, $globalListFile); }
    elsif ($record->{'articleListPublishGlobalOrCategoryIndexes'} eq 'category') { push(@publishFiles, $catListFile); }
  }

  elsif  ($record->{'type'} eq 'articlePages') {
    my $articleFile = $record->{'articlePagesFileName'}   . '.' . $record->{'articlePagesFileExt'};
    push(@publishFiles, $articleFile);
  }

  elsif  ($record->{'type'} eq 'singlePage') {
    my $singleFile = $record->{'singlePageFileName'}   . '.' . $record->{'singlePageFileExt'};
    push(@publishFiles, $singleFile);
  }

  # replace <ext> with global extension
  foreach my $filepath (@publishFiles) {
    $filepath =~ s/<ext>/$SETTINGS->{'publishFileExtension'}/gi;
  }

  ### define placeholder
  $attr->{'cellPlaceholders'}{'publishedAsFilenames'} = "";
  foreach my $file (@publishFiles) {
    my $cellPlaceholders = { 'filepath' => "$publishDir$file", 'filename' => "$file", 'num' => $record->{'num'} };
    $attr->{'cellPlaceholders'}{'publishedAsFilenames'} .= &templateCell('admin/menus/admin/publishRules.html', 'publishedAsFilename', $cellPlaceholders);
  }



}


# ------------------------------------------------------------------------------
# Function    : changeDisabledStatusOnRules
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::changeDisabledStatusOnRules();
# ------------------------------------------------------------------------------

sub changeDisabledStatusOnRules {
  my $menuName   = shift;
  my $isDisabled = shift;

  ### Get record numbers
  my $selectedRecordNumbers = &IT::App::Menu::getSelectedRecordNumbersAsHash();
  my $selectedRecordsAsCSV  = join ",", keys %$selectedRecordNumbers;  # CSV = Comma seperated values
  unless (keys %$selectedRecordNumbers) {
    my $error = $isDisabled ? "No records selected to disable!" : "No records selected to enable!";
    &IT::App::Menu::list($menuName, $error);
  }

  ### update records
  my $tableName  = $TABLE_PREFIX . "publishrules";
  my $whereQuery = "num IN ($selectedRecordsAsCSV)";
  my $updateHash = { 'disabled' => $isDisabled };

  &IT::FileLock::getLock('exclusive');
  &IT::DB::update($tableName, $whereQuery, $updateHash);
  &IT::FileLock::releaseLock();

  ### return to list page
  &IT::App::Menu::list($menuName);
  exit;

}

# ------------------------------------------------------------------------------
# Function    : showPlaceholderHelp
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::showPlaceholderHelp();
# ------------------------------------------------------------------------------

sub showPlaceholderHelp {

  my $placeholders = {};
  print &IT::Common::contentType('text/html');
  print &template('admin/popups/help/publishRulesPlaceholders.html', $placeholders);
  exit;

}

# ------------------------------------------------------------------------------
# Function    : createAddMenuDefaultValuePlaceholders
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::createAddMenuDefaultValuePlaceholders($attr);
# ------------------------------------------------------------------------------

sub createAddMenuDefaultValuePlaceholders {
  my $attr                = shift;
  my $placeholders        = $attr->{'placeholders'};

  # set default placeholder values
  $placeholders->{'type'}                                      ||= '';
  $placeholders->{'name'}                                      ||= '';
  $placeholders->{'description'}                               ||= '';
  $placeholders->{'template'}                                  ||= '';
  $placeholders->{'placeholder'}                               ||= '';
  $placeholders->{'disabled'}                                  ||= '0';
  $placeholders->{'categoryListPublishTopOrCategory'}          ||= 'top';
  $placeholders->{'categoryListTopMenuFileName'}               ||= 'include_globalMenu';
  $placeholders->{'categoryListTopMenuFileExt'}                ||= '<ext>';
  $placeholders->{'categoryListMenuFileName'}                  ||= '<catFile>/include_categoryMenu';
  $placeholders->{'categoryListMenuFileExt'}                   ||= '<ext>';
  $placeholders->{'articleListPublishGlobalOrCategoryIndexes'} ||= 'global';
  $placeholders->{'articleListGlobalFileName'}                 ||= 'index';
  $placeholders->{'articleListGlobalFileExt'}                  ||= '<ext>';
  $placeholders->{'articleListCategoryFileName'}               ||= '<catFile>/index';
  $placeholders->{'articleListCategoryFileExt'}                ||= '<ext>';
  $placeholders->{'articlePagesFileName'}                      ||= '<catFile>/<artFile>';
  $placeholders->{'articlePagesFileExt'}                       ||= '<ext>';
  $placeholders->{'articlePagesUseRuleForPreviews'}            ||= '0';
  $placeholders->{'singlePageFileName'}                        ||= 'pageName';
  $placeholders->{'singlePageFileExt'}                         ||= '<ext>';
  $placeholders->{'useCustomPublishDir'}                       ||= '0';
  $placeholders->{'customPublishDir'}                          ||= '';
  $placeholders->{'customPublishUrl'}                          ||= '';
  $placeholders->{'useCategoryFilter'}                         ||= '0';
  $placeholders->{'categoryFilterIncludeOrExclude'}            ||= 'include';
  $placeholders->{'categoryFilterSelections'}                  ||= '';
  $placeholders->{'limitResultsRangeStart'}                    ||= '1';
  $placeholders->{'limitResultsRangeEnd'}                      ||= '10';
  $placeholders->{'useCustomSorting'}                          ||= '0';
  $placeholders->{'customSortField1'}                          ||= '';
  $placeholders->{'customSortField2'}                          ||= '';
  $placeholders->{'customSortField3'}                          ||= '';
  $placeholders->{'customSortReverse1'}                        ||= '';
  $placeholders->{'customSortReverse2'}                        ||= '';
  $placeholders->{'customSortReverse3'}                        ||= '';
  $placeholders->{'useRecordFilter'}                           ||= '0';
  $placeholders->{'filterAllOrAny'}                            ||= 'all';
  $placeholders->{'filterField1'}                              ||= '';
  $placeholders->{'filterField2'}                              ||= '';
  $placeholders->{'filterField3'}                              ||= '';
  $placeholders->{'filterOperator1'}                           ||= '';
  $placeholders->{'filterOperator2'}                           ||= '';
  $placeholders->{'filterOperator3'}                           ||= '';
  $placeholders->{'filterValue1'}                              ||= '';
  $placeholders->{'filterValue2'}                              ||= '';
  $placeholders->{'filterValue3'}                              ||= '';
  $placeholders->{'categoryListFormat'}                        ||= 'showAll';
  $placeholders->{'customMenuVisibleDepthFromRootOnTopMenu'}   ||= '';
  $placeholders->{'customMenuVisibleDepthFromRootOnCatMenus'}  ||= '';
  $placeholders->{'customMenuVisibleDepthFromCurrentCategory'} ||= '';
  $placeholders->{'customMenuAncestorVisibility'}              ||= '';
  $placeholders->{'customMenuShowSelectedCategory'}            ||= '';
  $placeholders->{'externalUrl'}                               ||= '';

}

# ------------------------------------------------------------------------------
# Function    : createPulldownPlaceholders
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::createPulldownPlaceholders($attr);
# ------------------------------------------------------------------------------

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

  # create template options
  my $templateOptions = &_createTemplateOptions($attr);
  &IT::Template::setGlobalPlaceholder('templateOptions', $templateOptions);

  # create category options
  my $categoryOptions = &_createCategoryOptions($attr);
  &IT::Template::setGlobalPlaceholder('categoryOptions', $categoryOptions);

  # create record sorting field lists
  for my $num (1..3) {
    my $selectedValue = &firstDefinedValue($FORM->{"customSortField$num"}, $attr->{'recordFields'}{"customSortField$num"});
    my $fieldOptions  = &_createFieldNameOptions($selectedValue);
    &IT::Template::setGlobalPlaceholder("customSortFieldOptions$num", $fieldOptions);
  }

  # create record filter field lists
  for my $num (1..3) {
    my $selectedValue = &firstDefinedValue($FORM->{"filterField$num"}, $attr->{'recordFields'}{"filterField$num"});
    my $fieldOptions  = &_createFieldNameOptions($selectedValue);
    &IT::Template::setGlobalPlaceholder("filterFieldOptions$num", $fieldOptions);
  }


}

# ------------------------------------------------------------------------------
# Function    : _createTemplateOptions
# Description :
# Usage       : &_createTemplateOptions($attr);
# ------------------------------------------------------------------------------

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

  # get template list
  my @publishRuleTemplates;
  my $allTemplates = &IT::Template::getTemplateList();
  foreach my $template (sort @$allTemplates) {
    unless ($template =~ m/^(categoryMenus|articleLists|articlePages|singlePages)/) { next; }
    if ($template =~ /create_this_folder\.txt\$/) { next; }
    if ($template =~ /createThisDir\.txt\$/i) { next; }
    push @publishRuleTemplates, $template;
  }

  # generate template options
  my $templateOptions;
  foreach my $relativePath (@publishRuleTemplates) {
    my $selectedTemplate = &firstDefinedValue($FORM->{'template'}, $attr->{'recordFields'}{'template'});
    my $isSelected       = ($relativePath eq $selectedTemplate);
    my $cellPlaceholders = {};
    $cellPlaceholders->{'_optionValue'} = $relativePath;
    $cellPlaceholders->{'_optionLabel'} = "templates/$relativePath";
    $cellPlaceholders->{'_selected'}    = $isSelected ? ' selected="selected"' : '';

    $templateOptions .= &templateCell($template, 'templateOption', $cellPlaceholders);
  }

  return $templateOptions;

}

# ------------------------------------------------------------------------------
# Function    : _createCategoryOptions
# Description :
# Usage       : &_createCategoryOptions($attr);
# ------------------------------------------------------------------------------

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

  # get selected category nums
  my %selectedCategoryNumbers;
  if (exists $FORM->{'categoryFilterSelections'}) {
    %selectedCategoryNumbers = map { $_ => 1 } @{ $FORM_ARRAYS->{'categoryFilterSelections'} };
  }
  else {
    %selectedCategoryNumbers = map { $_ => 1 } split /\t/, $attr->{'recordFields'}{'categoryFilterSelections'};
  }

  # generate category options
  my $categoryOptions;
  my $sortedCategories = &IT::ArtMan::CustomMenus::Category::getSortedFilteredCategoryArray({});
  foreach my $category (@$sortedCategories) {
    my $categoryNum  = $category->{'num'};
    my $categoryName = $category->{'name'};
    my $isSelected   = $selectedCategoryNumbers{$categoryNum};

    my $cellPlaceholders = {};
    $cellPlaceholders->{'_optionValue'}    = $categoryNum;
    $cellPlaceholders->{'_optionLabel'}    = $categoryName;
    $cellPlaceholders->{'_breadcrumbText'} = &IT::ArtMan::CustomMenus::Category::generateTextBreadcrumb($categoryNum);

    $cellPlaceholders->{'_selected'}    = $isSelected ? ' selected="selected"' : '';

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

  return $categoryOptions;
}

# ------------------------------------------------------------------------------
# Function    : _createFieldNameOptions
# Description :
# Usage       : &_createFieldNameOptions($selectedFieldName);
# ------------------------------------------------------------------------------

sub _createFieldNameOptions {
  my $selectedFieldname = shift;

  # get sorted article field list
  unless ($ARTICLE_FIELD_LIST) {
    $ARTICLE_FIELD_LIST = [];
    my $articleTable   = $TABLE_PREFIX . "article";
    my @unsortedFields = &IT::DB::Schema::getFieldNames($articleTable);

    # get sort order
    my %fieldNameDisplayOrder;
    foreach my $fieldName (@unsortedFields) {
      $fieldNameDisplayOrder{$fieldName} = &IT::DB::Schema::getFieldAttributes($articleTable, $fieldName, 'appDisplayOrder');
    }

    @$ARTICLE_FIELD_LIST = sort { $fieldNameDisplayOrder{$a} <=> $fieldNameDisplayOrder{$b} } keys %fieldNameDisplayOrder;
  }
  my @fieldNames = @$ARTICLE_FIELD_LIST;


  # generate fieldname options
  my $template = "admin/menus/admin/publishRules.html";
  my $fieldNameOptions;
  foreach my $fieldName (@fieldNames) {
    my $isSelected   = $fieldName eq $selectedFieldname;

    my $cellPlaceholders = {};
    $cellPlaceholders->{'_optionValue'} = $fieldName;
    $cellPlaceholders->{'_optionLabel'} = $fieldName;
    $cellPlaceholders->{'_selected'}    = $isSelected ? ' selected="selected"' : '';

    $fieldNameOptions .= &templateCell($template, 'fieldNameOption', $cellPlaceholders);
  }

  return $fieldNameOptions;
}

# ------------------------------------------------------------------------------
# Function    :
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::saveErrorChecking($attr);
# ------------------------------------------------------------------------------

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

  &IT::App::demoModeAlertAndAction("Saving publish rules is disabled in demo mode.", sub { &IT::App::Menu::list('publishRules') });

  ### All Rules
  if ($FORM->{'name'} eq '')        { push @errors, "Rule Name is required!"; }
  if ($FORM->{'type'} eq '')        { push @errors, "Rule Type is required!"; }
  unless ($FORM->{'type'} eq 'externalUrl') {
    if ($FORM->{'template'} eq '')    { push @errors, "Template is required!"; }
    if ($FORM->{'placeholder'} eq '') { push @errors, "Placeholder is required!"; }
    if ($FORM->{'placeholder'} =~ /[^A-Za-z0-9\_]/) { push @errors, "Placeholder can only contain the following characters (A-Za-z0-9_)!"; }
  }

  push @errors, &_getArticleListErrors();
  push @errors, &_getArticlePageErrors();
  push @errors, &_getExternalUrlErrors();
  push @errors, &_getFilenamePlaceholderErrors();
  push @errors, &_getFilenameErrors();
  push @errors, &_getCategoryFilterErrors();



  # return errors;
  my $errors = join "\n", grep { $_ ne '' } @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    : _getArticleListErrors
# Description :
# Usage       : push @errors, &_getArticleListErrors()
# ------------------------------------------------------------------------------

sub _getArticleListErrors {
  my @errors;

  # skip all but article pages
  if ($FORM->{'type'} ne 'articleList') { return; }

  push @errors, &IT::Validation::validationError("Limit Results Start Value", $FORM->{'limitResultsRangeStart'}, 'notBlank positiveInt minNumber(1)');
  push @errors, &IT::Validation::validationError("Limit Results End Value",   $FORM->{'limitResultsRangeEnd'},   'notBlank positiveInt minNumber(1)');

  return @errors;
}

# ------------------------------------------------------------------------------
# Function    : _getArticlePageErrors
# Description :
# Usage       : push @errors, &_getArticlePageErrors()
# ------------------------------------------------------------------------------

sub _getArticlePageErrors {
  my @errors;

  # skip all but article pages
  if ($FORM->{'type'} ne 'articlePages') { return; }

  ### only allow one preview publish rule
  if ($FORM->{'articlePagesUseRuleForPreviews'}) {

    # load current preview rule(s)
    &IT::FileLock::getLock('shared');
    my $previewRules = &IT::DB::select({
      'tableName'  => $TABLE_PREFIX . 'publishrules',
      'whereQuery' => "articlePagesUseRuleForPreviews = '1'",
    });
    &IT::FileLock::releaseLock();

    # check for other preview rules
    foreach my $previewRule (@$previewRules) {
      if ($previewRule->{'num'} != $FORM->{'num'}) {
        push @errors, "Article Previews are already being generated with the rule: '$previewRule->{'name'}'.";
        push @errors, "You must disabled Article Previews in that rule first before enabling them in this rule.";
        last;
      }
    }
  }

  return @errors;
}

# ------------------------------------------------------------------------------
# Function    : _getExternalUrlErrors
# Description :
# Usage       : push @errors, &_getExternalUrlErrors()
# ------------------------------------------------------------------------------

sub _getExternalUrlErrors {
  my @errors;

  # skip all but article pages
  if ($FORM->{'type'} ne 'externalUrl') { return; }

  my $isValidUrl = &IT::Validation::isValidUrl($FORM->{'externalUrl'});
  if    (!$FORM->{'externalUrl'})               { push @errors, "You must enter an url!"; }
  elsif ($FORM->{'externalUrl'} !~ m|^http://|) { push @errors, "External Url must start with http://"; }
  elsif (!$isValidUrl)                          { push @errors, "The specified url isn't valid! Valid Example: http://www.example.com/path/file.html"; }

  return @errors;
}

# ------------------------------------------------------------------------------
# Function    : _getCategoryFilterErrors
# Description :
# Usage       : push @errors, &_getCategoryFilterErrors()
# ------------------------------------------------------------------------------

sub _getCategoryFilterErrors {
  my @errors;

  # skip rules that don't use or support category filter
  my $isRuleUsingCategoryFilter = 0;
  unless ($FORM->{'useCategoryFilter'}) { return; }
  unless ($FORM->{'type'} eq 'categoryMenu' || $FORM->{'type'} eq 'articleList' || $FORM->{'type'} eq 'articlePages') { return; }

  # get category count
  my @categoryFilterSelections = @{$FORM_ARRAYS->{'categoryFilterSelections'}} if $FORM_ARRAYS->{'categoryFilterSelections'};
  my $selectedCategoryNums = &IT::ArtMan::CustomMenus::Admin::PublishRules::getSelectedCategoryNumsForPublishRule({
    'useCategoryFilter'              => 1,                                         # if not 1, all category nums will be returned.
    'categoryFilterIncludeOrExclude' => $FORM->{'categoryFilterIncludeOrExclude'},
    'categoryFilterSelections'       => join("\t", @categoryFilterSelections),
  });
  my $categoryCount = @$selectedCategoryNums;

  if ($categoryCount == 0) {
    if ($FORM->{'categoryFilterIncludeOrExclude'} eq 'exclude') { push @errors, "Category Filter is selected but excludes ALL categories!"; }
    else                                                        { push @errors, "Category Filter is selected but doesn't include any categories!"; }
  }

  return @errors;
}



# ------------------------------------------------------------------------------
# Function    : _getFilenamePlaceholderErrors
# Description : Rule can't use filename placeholders that aren't available to it.
# Usage       : push @errors, &_getFilenamePlaceholderErrors();
# ------------------------------------------------------------------------------

sub _getFilenamePlaceholderErrors {
  my @errors;

  ### Skip if no rule-type defined
  if (!$FORM->{'type'}) { return; }
  if ($FORM->{'type'} eq 'externalUrl') { return; }

  ### check for non-allowed filename placeholders

  # category menus
  my($filenameFormat, $singleOrMultiFile) = &IT::ArtMan::CustomMenus::Admin::PublishRules::getFilenameFormatAndType($FORM);
  if ($FORM->{'type'} eq 'categoryMenu') {
    if ($singleOrMultiFile eq 'single') {
      while ($filenameFormat =~ /<(artNum|artFile|catNum|catFile)>/ig) { push @errors, "Filename error: global category menus can't use <$1> filename placeholders!"; }
    }
    elsif ($singleOrMultiFile eq 'multi') {
      while ($filenameFormat =~ /<(artNum|artFile)>/ig)                { push @errors, "Filename error: category menus can't use <$1> filename placeholders!"; }
    }
  }

  # article list
  elsif ($FORM->{'type'} eq 'articleList')  {
    if ($singleOrMultiFile eq 'single') {
      while ($filenameFormat =~ /<(artNum|artFile|catNum|catFile)>/ig) { push @errors, "Filename error: global article lists can't use <$1> filename placeholders!"; }
    }
    elsif ($singleOrMultiFile eq 'multi') {
      while ($filenameFormat =~ /<(artNum|artFile)>/ig)                { push @errors, "Filename error: article lists can't use <$1> filename placeholders!"; }
    }
  }

  # article pages
  elsif ($FORM->{'type'} eq 'articlePages') { 1; }  # all placeholders allowed

  # single pages
  elsif ($FORM->{'type'} eq 'singlePage') {
    while ($filenameFormat =~ /<(artNum|artFile|catNum|catFile)>/ig)   { push @errors, "Filename error: singe pages can't use <$1> filename placeholders!"; }
  }
  else { die "unknown rule type of '$FORM->{'type'}'!"; }

  return @errors;
}

# ------------------------------------------------------------------------------
# Function    : _getFilenameErrors
# Description : Single file publish rules can't have the same name as another
#               rule and multi-file publish rules must have filename placeholders
#               in their names.
# Usage       : push @errors, &_getFilenameErrors();
# ------------------------------------------------------------------------------

sub _getFilenameErrors {
  my @errors;

  ### Skip if no rule-type defined
  if (!$FORM->{'type'}) { return; }
  if ($FORM->{'type'} eq 'externalUrl') { return; }

  # load vars
  my($formFilenameFormat, $formSingleOrMultiFile) = &IT::ArtMan::CustomMenus::Admin::PublishRules::getFilenameFormatAndType($FORM);
  my($publishDir, $publishUrl)                    = &IT::ArtMan::CustomMenus::Admin::PublishRules::getPublishDirAndUrl($FORM);
  my $publishRulesArray                           = &IT::ArtMan::CustomMenus::Admin::PublishRules::getSortedArrayOfPublishRules();

  ### Check filenames are defined (and don't start with ".";
  if ($formFilenameFormat =~ m/^\./) {
    push @errors, "Filename '$formFilenameFormat' cannot start with a '.'";
  }


  ### Check multi-page publish rules use filename placeholder
  if ($FORM->{'type'} eq 'articlePages') {
    if ($formFilenameFormat !~ m/<artNum|artFile>/ig) {
      push @errors, "The selected rule publishes multiple article files.  You must include <artNum> or <artFile> in the filename field so that each published file has a unique name and published files don't overwrite each other.";
    }
  }
  elsif ($formSingleOrMultiFile eq 'multi') {
    if ($formFilenameFormat !~ m/<artNum|artFile|catNum|catFile>/ig) {
      push @errors, "The selected rule publishes multiple files.  You must use one or more filename placeholders so that each published file has a unique name and published files don't overwrite each other.";
    }
  }

  ### Check multi-page publish have a unique filename AND placeholder (for their categories)
  if ($formSingleOrMultiFile eq 'multi') {
    my @formCategoryFilterSelections = @{$FORM_ARRAYS->{'categoryFilterSelections'}} if $FORM_ARRAYS->{'categoryFilterSelections'};
    my $formCategoryNums             = &IT::ArtMan::CustomMenus::Admin::PublishRules::getSelectedCategoryNumsForPublishRule({
      'useCategoryFilter'              => $FORM->{'useCategoryFilter'},              # return selected categories or all if useCategoryFilter isn't checked
      'categoryFilterIncludeOrExclude' => $FORM->{'categoryFilterIncludeOrExclude'},
      'categoryFilterSelections'       => join("\t", @formCategoryFilterSelections),
    });
    my %formCategoryNums = map { $_ => 1 } @$formCategoryNums;

    foreach my $publishRule (@$publishRulesArray) {
      my($ruleFilenameFormat, $ruleSingleOrMultiFile) = &IT::ArtMan::CustomMenus::Admin::PublishRules::getFilenameFormatAndType($publishRule);
      my($publishDir, $publishUrl)                    = &IT::ArtMan::CustomMenus::Admin::PublishRules::getPublishDirAndUrl($publishRule);
      if ($publishRule->{'num'} eq $FORM->{'num'}) { next; }  # don't compare to ourselves

      # get list of matching categories
      my $ruleCategoryNums        = &IT::ArtMan::CustomMenus::Admin::PublishRules::getSelectedCategoryNumsForPublishRule($publishRule);
      my @matchingCategoryNums    = grep { $formCategoryNums{$_} } @$ruleCategoryNums;
      my $matchingCategoriesAsCSV;
      foreach my $matchingCategoryNum (@matchingCategoryNums) {
        $matchingCategoriesAsCSV .= " - " . &IT::ArtMan::CustomMenus::Category::generateTextBreadcrumb($matchingCategoryNum) . "\n";
      }

      # check for unique placeholder
      if ($matchingCategoriesAsCSV && lc $FORM->{'placeholder'} eq lc $publishRule->{'placeholder'}) {
        push @errors, "This publish rule has the same categories and placeholder as '$publishRule->{'name'}'. Either unselect the matching categories or change the placeholder name. Matching categories:";
        push @errors, "$matchingCategoriesAsCSV";
      }

      # check for unique filename
      if ($matchingCategoriesAsCSV && lc "$publishDir$formFilenameFormat" eq lc "$publishDir$ruleFilenameFormat") {
        push @errors, "This publish rule has the same categories and filename as ($publishRule->{'name'}). Either unselect the matching categories or change the filename. Matching categories:";
        push @errors, "$matchingCategoriesAsCSV";
      }
    }
  }

  ### Check single-page publish rules have unique filename and placeholder
  if ($formSingleOrMultiFile eq 'single') {
    foreach my $publishRule (@$publishRulesArray) {
      my($ruleFilenameFormat, $ruleSingleOrMultiFile) = &IT::ArtMan::CustomMenus::Admin::PublishRules::getFilenameFormatAndType($publishRule);
      my($publishDir, $publishUrl)                    = &IT::ArtMan::CustomMenus::Admin::PublishRules::getPublishDirAndUrl($publishRule);
      if ($publishRule->{'num'} eq $FORM->{'num'}) { next; }  # don't compare to ourselves

      # check for unique placeholder
      if ($FORM->{'placeholder'} && lc $FORM->{'placeholder'} eq lc $publishRule->{'placeholder'}) {
        push @errors, "Placeholder is already in use by the following publish rule: $publishRule->{'name'}";
      }

      # check for unique filename
      if (lc "$publishDir$formFilenameFormat" eq lc "$publishDir$ruleFilenameFormat") {
        push @errors, "Filename is already in use by the following publish rule: $publishRule->{'name'}";
      }
    }
  }

  return @errors;
}

# ------------------------------------------------------------------------------
# Function    : saveAndCopy
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::saveAndCopy($menuName);
# ------------------------------------------------------------------------------

sub saveAndCopy {
  my $menuName = shift || die "no menuname specified!";

  ### redirect to add page on postsave
  &IT::App::Menu::setMenuAttribute($menuName, 'save', 'postSave',  sub {

    # change form values
    $FORM->{'num'}  = '';
    $FORM->{'name'} = "Copy of $FORM->{'name'}";

    # display add form
    &IT::App::Menu::add($menuName)
  });

  # save record
  &IT::App::Menu::save($menuName);

}

# ------------------------------------------------------------------------------
# Function    : convertSSIsToPHPIncludes
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::convertSSIsToPHPIncludes($menuName);
# ------------------------------------------------------------------------------

sub convertSSIsToPHPIncludes {
  my $menuName = shift || die "no menuname specified!";

  &IT::App::demoModeAlertAndAction("Converting SSIs to PHP includes is disabled in demo mode.", sub { &IT::App::Menu::list($menuName); });

  # replace includes
  my $filepathRegexp = 'templates/'; # match all templates
  my $matchRegexp    = q|<!--#include virtual=['"]\$published\.(.*?)\.webpath\$['"] -->|;
  my $replacement    = q|<\?php include('\$published.$1.filepath\$'); \?>|;
  my($replacementsMade, $templatesModified) = &IT::Template::searchAndReplace($filepathRegexp, $matchRegexp, $replacement);

  # display list page
  my $message = "$templatesModified templates updated: $replacementsMade SSIs replaced with PHP includes.";
  &IT::App::Menu::list($menuName, $message);
}

# ------------------------------------------------------------------------------
# Function    : convertPHPIncludesToSSIs
# Description :
# Usage       : &IT::ArtMan::CustomMenus::Admin::PublishRules::convertPHPIncludesToSSIs($menuName);
# ------------------------------------------------------------------------------

sub convertPHPIncludesToSSIs {
  my $menuName = shift || die "no menuname specified!";

  &IT::App::demoModeAlertAndAction("Converting PHP includes to SSIs is disabled in demo mode.", sub { &IT::App::Menu::list($menuName); });

  # replace includes
  my $filepathRegexp = 'templates/'; # match all templates
  my $matchRegexp    = q|<\?php\s+include\(['"]\$published\.(.*?)\.filepath\$['"]\);?\s*\?>|;
  my $replacement    = q|<!--#include virtual="\$published.$1.webpath\$" -->|;
  my($replacementsMade, $templatesModified) = &IT::Template::searchAndReplace($filepathRegexp, $matchRegexp, $replacement);

  # display list page
  my $message = "$templatesModified templates updated: $replacementsMade PHP includes replaced with SSIs.";
  &IT::App::Menu::list($menuName, $message);
}

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

