Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/automations/model/_automation.class.php
<?php
/**
 * This file implements the automation class.
 *
 * This file is part of the b2evolution/evocms project - {@link http://b2evolution.net/}.
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}.
*
 * @license http://b2evolution.net/about/license.html GNU General Public License (GPL)
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );

load_class( '_core/model/dataobjects/_dataobject.class.php', 'DataObject' );


/**
 * Automation Class
 *
 * @package evocore
 */
class Automation extends DataObject
{
    var
$name;
    var
$status;
    var
$owner_user_ID;

    var
$newsletters = NULL;
    var
$owner_User = NULL;

   
/**
     * Constructor
     *
     * @param object table Database row
     */
   
function __construct( $db_row = NULL )
    {
       
// Call parent constructor:
       
parent::__construct( 'T_automation__automation', 'autm_', 'autm_ID' );

        if(
$db_row === NULL )
        {
            if(
is_logged_in() )
            {    
// Use current User for new creating Automation:
               
global $current_User;
               
$this->owner_User = $current_User;
            }
        }
        else
        {
           
$this->ID = $db_row->autm_ID;
           
$this->name = $db_row->autm_name;
           
$this->status = $db_row->autm_status;
           
$this->owner_user_ID = $db_row->autm_owner_user_ID;
        }
    }


   
/**
     * Get delete cascade settings
     *
     * @return array
     */
   
static function get_delete_cascades()
    {
        return array(
                array(
'table' => 'T_automation__step', 'fk' => 'step_autm_ID', 'msg' => T_('%d steps') ),
                array(
'table' => 'T_automation__user_state', 'fk' => 'aust_autm_ID', 'msg' => T_('%d states of User in Automation') ),
                array(
'table' => 'T_automation__newsletter', 'fk' => 'aunl_autm_ID', 'msg' => T_('%d automation associations with lists') ),
            );
    }


   
/**
     * Get delete restriction settings
     *
     * @return array
     */
   
static function get_delete_restrictions()
    {
        return array(
                array(
'table' => 'T_automation__step', 'fk' => 'step_info', 'and_condition' => 'step_type = "start_automation" AND step_autm_ID != $this_ID$', 'msg' => T_('Automation is used by %s steps of other automations') ),
            );
    }


   
/**
     * Insert object into DB based on previously recorded changes.
     *
     * @return boolean true on success
     */
   
function dbinsert()
    {
        if(
$r = parent::dbinsert() )
        {
           
// Update newsletters links with this Automation:
           
$this->update_newsletters();
        }

        return
$r;
    }


   
/**
     * Update the DB based on previously recorded changes
     *
     * @return boolean true on success
     */
   
function dbupdate()
    {
       
$r = parent::dbupdate();

       
// NOTE: Don't a result of dbupdate() to update newsletters links,
        // because it can be false if ONLY newsletters links have been changed to the edit submitted form:
        // Update newsletters links with this Automation:
       
$this->update_newsletters();

        return
$r;
    }


   
/**
     * Update newsletters links with Automation
     *
     * @return integer|boolean A count of new inserted links between automation and newsletters, FALSE - on wrong insert data
     */
   
function update_newsletters()
    {
        if( empty(
$this->update_newsletters ) )
        {    
// This action is not requested:
           
return false;
        }

        if( empty(
$this->newsletters ) )
        {    
// At least one newsletter must be defined:
           
return false;
        }

       
$sql_newsletters_values = array();
       
$aunl_order = 1;
        foreach(
$this->newsletters as $newsletter )
        {
           
$newsletter_ID = intval( $newsletter['ID'] );
            if( empty(
$newsletter_ID ) )
            {    
// Skip wrong newsletter data:
               
continue;
            }
           
// Build array with newsletter ID as key to avoid duplicate entry mysql error:
           
$sql_newsletters_values[ $newsletter_ID ] = '( '.$this->ID.', '.$newsletter_ID.', '.intval( $newsletter['autostart'] ).', '.intval( $newsletter['autoexit'] ).', '.( $aunl_order++ ).' )';
        }

        if( empty(
$sql_newsletters_values ) )
        {    
// At least one newsletter must be inserted:
           
return false;
        }

        global
$DB;

       
// Delete previous newsletter links:
       
$DB->query( 'DELETE FROM T_automation__newsletter
            WHERE aunl_autm_ID = '
.$this->ID );

       
// Insert new newsletter links for this automation:
       
$r = $DB->query( 'INSERT INTO T_automation__newsletter ( aunl_autm_ID, aunl_enlt_ID, aunl_autostart, aunl_autoexit, aunl_order )
            VALUES '
.implode( ', ', $sql_newsletters_values ) );

        return
$r;
    }


   
/**
     * Load data from Request form fields.
     *
     * @return boolean true if loaded data seems valid.
     */
   
function load_from_Request()
    {
       
// Name:
       
param_string_not_empty( 'autm_name', T_('Please enter an automation name.') );
       
$this->set_from_Request( 'name' );

       
// Status:
       
param_string_not_empty( 'autm_status', 'Please select an automation status.' );
       
$this->set_from_Request( 'status' );

       
// Tied to Lists:
       
$prev_newsletters = $this->get_newsletters();
       
$updated_newsletters = array();
       
$aunl_enlt_IDs = param( 'aunl_enlt_ID', 'array:integer', array() );
        foreach(
$aunl_enlt_IDs as $n => $aunl_enlt_ID )
        {
            if(
$aunl_enlt_ID > 0 )
            {
               
$updated_newsletters[ $aunl_enlt_ID ] = array(
                       
'ID'        => (string)$aunl_enlt_ID,
                       
'autostart' => (string)param( 'aunl_autostart_'.$n, 'integer', 0 ),
                       
'autoexit'  => (string)param( 'aunl_autoexit_'.$n, 'integer', 0 ),
                    );
            }
        }
        if( empty(
$updated_newsletters ) )
        {    
// Display an error and fill first required tied list:
           
param_error( 'aunl_enlt_ID[]', T_('Please select an automation list.') );
           
$this->newsletters = array( array(
                   
'ID'        => '0',
                   
'autostart' => param( 'aunl_autostart_0', 'integer', 0 ),
                   
'autoexit'  => param( 'aunl_autoexit_0', 'integer', 0 ),
                ) );
        }
        else
        {    
// Update newsletters array with new entered values:
           
$updated_newsletters = array_values( $updated_newsletters );
            if(
$prev_newsletters !== $updated_newsletters )
            {    
// Set flag to update newsletters:
               
$this->update_newsletters = true;
               
$this->newsletters = $updated_newsletters;
            }
        }

       
// Owner:
       
$autm_owner_login = param( 'autm_owner_login', 'string', NULL );
       
$UserCache = & get_UserCache();
       
$owner_User = & $UserCache->get_by_login( $autm_owner_login );
        if( empty(
$owner_User ) )
        {
           
param_error( 'owner_login', sprintf( T_('User &laquo;%s&raquo; does not exist!'), $autm_owner_login ) );
        }
        else
        {
           
$this->set( 'owner_user_ID', $owner_User->ID );
           
$this->owner_User = & $owner_User;
        }

        return !
param_errors_detected();
    }


   
/**
     * Get name of automation
     *
     * @return string Name of automation
     */
   
function get_name()
    {
        return
$this->get( 'name' );
    }


   
/**
     * Get user states for current time(automation steps which should be executed immediately)
     *
     * @return array Array( user_ID => next_step_ID )
     */
   
function get_user_states()
    {
        global
$DB, $servertimenow;

        if( empty(
$this->ID ) )
        {    
// Automation must be stored in DB:
           
return array();
        }

       
$SQL = new SQL( 'Get user states for current time of automation #'.$this->ID );
       
$SQL->SELECT( 'aust_user_ID, aust_next_step_ID' );
       
$SQL->FROM( 'T_automation__user_state' );
       
$SQL->WHERE( 'aust_autm_ID = '.$this->ID );
       
$SQL->WHERE_and( 'aust_next_step_ID IS NOT NULL ' );
       
$SQL->WHERE_and( 'aust_next_exec_ts <= '.$DB->quote( date2mysql( $servertimenow ) ) );

        return
$DB->get_assoc( $SQL );
    }


   
/**
     * Get owner User
     *
     * @return object|NULL|boolean Reference on cached owner User object, NULL - if request with empty ID, FALSE - if requested owner User does not exist
     */
   
function & get_owner_User()
    {
        if(
$this->owner_User === NULL )
        {    
// Load owner User into cache var:
           
$UserCache = & get_UserCache();
           
$this->owner_User = & $UserCache->get_by_ID( $this->owner_user_ID, false, false );
        }

        return
$this->owner_User;
    }


   
/**
     * Get Newsletter
     *
     * @param integer Number of newsletter
     * @return object|NULL|boolean Reference on cached Newsletter object, NULL - if request with empty ID, FALSE - if requested Newsletter does not exist
     */
   
function & get_Newsletter( $number = 1 )
    {
       
$newsletters = $this->get_newsletters();

        if( ! isset(
$newsletters[ $number ] ) || ! isset( $newsletters[ $number ]['ID'] ) )
        {    
// No detected newsletter by number:
           
$r = false;
            return
$r;
        }

       
// Get Newsletter from DB or cache:
       
$NewsletterCache = & get_NewsletterCache();
       
$Newsletter = & $NewsletterCache->get_by_ID( $newsletters[ $number ]['ID'], false, false );

        return
$Newsletter;
    }


   
/**
     * Get settings of all newsletters for this Automation
     *
     * @return array Newsletters array of array with keys: 'ID', 'autostart', 'autoexit'
     */
   
function get_newsletters()
    {
        if(
$this->newsletters === NULL )
        {    
// Load newsletters settings once:
           
if( empty( $this->ID ) )
            {    
// Set default first newsletter setting:
               
$this->newsletters = array( array( 'ID' => '', 'autostart' => 1, 'autoexit' => 1 ) );
            }
            else
            {    
// Get newsletters settings from DB:
               
global $DB;
               
$SQL = new SQL( 'Load newsletters settings for Automation #'.$this->ID );
               
$SQL->SELECT( 'aunl_enlt_ID AS ID, aunl_autostart AS autostart, aunl_autoexit AS autoexit' );
               
$SQL->FROM( 'T_automation__newsletter' );
               
$SQL->WHERE( 'aunl_autm_ID = '.$this->ID );
               
$SQL->ORDER_BY( 'aunl_order' );
               
$this->newsletters = $DB->get_results( $SQL, ARRAY_A );
            }
        }

        return
$this->newsletters;
    }


   
/**
     * Get IDs of all newsletters of this Automation
     *
     * @return array Newsletters IDs
     */
   
function get_newsletter_IDs()
    {
       
$newsletters = $this->get_newsletters();

       
$newsletter_IDs = array();
        foreach(
$newsletters as $newsletter )
        {
           
$newsletter_IDs[] = $newsletter['ID'];
        }

        return
$newsletter_IDs;
    }


   
/**
     * Add users to this automation
     *
     * @param array|integer IDs of users
     * @param array Params
     * @return integer Number of added users
     */
   
function add_users( $user_IDs, $params = array() )
    {
        global
$DB, $servertimenow;

       
$params = array_merge( array(
               
'users_no_subs'   => 'ignore', // Action for users who are not subscribed to Newsletter of this Automation: 'ignore' - Ignore, 'add' - Add anyway
               
'users_automated' => 'ignore', // Action for users who are already in this Automation: 'ignore' - Ignore, 'requeue' - Requeue to Start
               
'users_new'       => 'add', // Action for new users: 'ignore' - Ignore, 'add' - Add to automation
               
'newsletter_IDs'   => NULL, // Newsletter IDs to ignore not subscribed users, NULL - to use any tied newsletter
           
), $params );

       
$added_users_num = 0;

        if( empty(
$this->ID ) )
        {    
// Automation must be stored in DB:
           
return $added_users_num;
        }

        if( empty(
$user_IDs ) )
        {    
// No users to add:
           
return $added_users_num;
        }

        if( !
is_array( $user_IDs ) )
        {
           
$user_IDs = array( $user_IDs );
        }

        if(
$params['users_no_subs'] == 'ignore' )
        {    
// Ignore not subscribed users to this Automation:

            // Get newsletter IDs:
           
$newsletter_IDs = ( $params['newsletter_IDs'] === NULL ? $this->get_newsletter_IDs() : $params['newsletter_IDs'] );
            if( !
is_array( $newsletter_IDs ) )
            {    
// If single newsletter is given:
               
$newsletter_IDs = array( $newsletter_IDs );
            }

           
$no_subs_SQL = new SQL( 'Get not subscribed users of the Automation #'.$this->ID );
           
$no_subs_SQL->SELECT( 'user_ID' );
           
$no_subs_SQL->FROM( 'T_users' );
           
$no_subs_SQL->FROM_add( 'LEFT JOIN T_email__newsletter_subscription ON enls_user_ID = user_ID AND enls_enlt_ID IN ( '.$DB->quote( $newsletter_IDs ).' )' );
           
$no_subs_SQL->WHERE( 'user_ID IN ( '.$DB->quote( $user_IDs ).' )' );
           
$no_subs_SQL->WHERE_and( 'enls_subscribed = 0 OR enls_user_ID IS NULL' );
           
// Remove not subscribed users from array:
           
$user_IDs = array_diff( $user_IDs, $DB->get_col( $no_subs_SQL ) );
        }
       
// else: Add not subscribed users anyway

       
if( empty( $user_IDs ) )
        {    
// No users to add, Stop here:
           
return $added_users_num;
        }

       
$automated_SQL = new SQL( 'Get users of the Automated #'.$this->ID );
       
$automated_SQL->SELECT( 'aust_user_ID' );
       
$automated_SQL->FROM( 'T_automation__user_state' );
       
$automated_SQL->WHERE( 'aust_autm_ID = '.$this->ID );
       
$automated_SQL->WHERE_and( 'aust_user_ID IN ( '.$DB->quote( $user_IDs ).' )' );
       
$automated_user_IDs = $DB->get_col( $automated_SQL );

       
// Remove already automated users from array:
       
$user_IDs = array_diff( $user_IDs, $automated_user_IDs );


        if(
count( $automated_user_IDs ) || count( $user_IDs ) )
        {    
// Get first Step of this Automation:
           
$first_step_SQL = new SQL( 'Get first step of automation #'.$this->ID );
           
$first_step_SQL->SELECT( 'step_ID' );
           
$first_step_SQL->FROM( 'T_automation__step' );
           
$first_step_SQL->WHERE( 'step_autm_ID = '.$this->ID );
           
$first_step_SQL->ORDER_BY( 'step_order ASC' );
           
$first_step_SQL->LIMIT( 1 );
           
$first_step_ID = $DB->get_var( $first_step_SQL );
        }

        if(
$params['users_automated'] == 'requeue' && count( $automated_user_IDs ) )
        {    
// Requeue already automated users to first Step:
           
$added_users_num += $DB->query( 'UPDATE T_automation__user_state
                  SET aust_next_step_ID = '
.$DB->quote( intval( $first_step_ID ) ).',
                      aust_next_exec_ts = '
.$DB->quote( date2mysql( $servertimenow ) ).'
                WHERE aust_autm_ID = '
.$DB->quote( $this->ID ).'
                  AND aust_user_ID IN ( '
.$DB->quote( $automated_user_IDs ).' )',
               
'Requeue already automated users to first Step from users list' );
        }
       
// else:  Ignore already automated users

       
if( $params['users_new'] == 'add' && count( $user_IDs ) )
        {    
// Add new users to this Automation:
           
$insert_sql = array();
            foreach(
$user_IDs as $user_ID )
            {
               
$insert_sql[] = '( '.$DB->quote( $this->ID ).', '.$DB->quote( $user_ID ).', '.$DB->quote( $first_step_ID ).', '.$DB->quote( date2mysql( $servertimenow ) ).' )';
            }
           
$added_users_num += $DB->query( 'INSERT INTO T_automation__user_state ( aust_autm_ID, aust_user_ID, aust_next_step_ID, aust_next_exec_ts )
                VALUES '
.implode( ', ', $insert_sql ),
               
'Insert automation user states from users list' );
        }

        return
$added_users_num;
    }


   
/**
     * Check if user is subscribed to at least one tied Newsletter of this Automation
     *
     * @param integer User ID
     * @return integer|boolean ID of first tied newsletter where the requested user is subscribed, FALSE - user is not subscribed
     */
   
function is_user_subscribed( $user_ID )
    {
       
$newsletter_IDs = $this->get_newsletter_IDs();

        if( empty(
$newsletter_IDs ) )
        {    
// No automation newsletters found:
           
return false;
        }

       
$NewsletterCache = & get_NewsletterCache();
       
// Preload all automation newsletters by single query:
       
$NewsletterCache->load_list( $newsletter_IDs );

        foreach(
$newsletter_IDs as $newsletter_ID )
        {
            if( (
$automation_Newsletter = & $NewsletterCache->get_by_ID( $newsletter_ID, false, false ) ) &&
               
in_array( $user_ID, $automation_Newsletter->get_user_IDs() ) )
            {    
// If user is subscribed to first tied newsletter, Stop find other subscriptions:
               
return $automation_Newsletter->ID;
            }
        }

       
// User is not subscribed to any tied newsletter of this Automation:
       
return false;
    }


   
/**
     * Get steps data for diagram view
     *
     * @return array Steps
     */
   
function get_diagram_steps_data()
    {
       
$steps = array();

       
$AutomationStepCache = & get_AutomationStepCache();
       
$AutomationStepCache->load_where( 'step_autm_ID = '.$this->ID );

        if( empty(
$AutomationStepCache->cache ) )
        {    
// Automation has no steps yet:
           
return $steps;
        }

       
$step_results = array( 'YES', 'NO', 'ERROR' );
       
$step_result_labels = step_get_result_labels();

        foreach(
$AutomationStepCache->cache as $AutomationStep )
        {
           
$step = array(
                   
'order'      => $AutomationStep->get( 'order' ),
                   
'type'       => $AutomationStep->get( 'type' ),
                   
'label'      => $AutomationStep->get( 'label' ),
                   
'next_steps' => array(),
                );
           
// Fill data of next steps to initialise connectors between step boxes by JS code below:
           
if( $yes_next_AutomationStep = & $AutomationStep->get_yes_next_AutomationStep() )
            {    
// Next YES step:
               
$step['next_steps']['yes'] = $yes_next_AutomationStep->ID;
            }
            if(
$no_next_AutomationStep = & $AutomationStep->get_no_next_AutomationStep() )
            {    
// Next NO step:
               
$step['next_steps']['no'] = $no_next_AutomationStep->ID;
            }
            if(
$error_next_AutomationStep = & $AutomationStep->get_error_next_AutomationStep() )
            {    
// Next ERROR step:
               
$step['next_steps']['error'] = $error_next_AutomationStep->ID;
            }

           
$step['attrs'] = array(
                   
'id'    => 'step_'.$AutomationStep->ID,
                   
'class' => 'evo_automation__diagram_step_box',
                );

           
$step_type_result_labels = $step_result_labels[ $AutomationStep->get( 'type' ) ];

            foreach(
$step_results as $step_result )
            {    
// Initialize step data for each result type(YES|NO|ERROR):
               
if( ! empty( $step_type_result_labels[ $step_result ] ) )
                {
                   
$step['attrs']['data-info-'.strtolower( $step_result ) ] = str_replace( 'Next step if ', '', $step_type_result_labels[ $step_result ] )
                        .
' ('.seconds_to_period( $AutomationStep->get( strtolower( $step_result ).'_next_step_delay' ), true ).')';
                }
            }

           
// Get stored position from DB:
           
$position = $AutomationStep->get( 'diagram' );
            if(
$position !== NULL )
            {
               
$position = explode( ':', $position );
                if(
count( $position ) == 2 )
                {
                   
$step['attrs']['style'] = 'left:'.$position[0].'px;top:'.$position[1].'px';
                }
            }

           
$steps[ $AutomationStep->ID ] = $step;
        }

       
// Set default positions for step boxes when they are not stored in DB yet:
       
$this->set_diagram_default_steps_positions( $steps );

        return
$steps;
    }


   
/**
     * Set default steps positions for diagram view
     *
     * @param array Steps data (by reference)
     */
   
function set_diagram_default_steps_positions( & $steps )
    {
        if( empty(
$steps ) )
        {    
// No steps:
           
return $steps;
        }

       
// Set temp array for diagram functions:
       
$this->diagram_steps = $steps;

        foreach(
$this->diagram_steps as $step_ID => $step )
        {    
// Start with first step:
           
$this->resolve_diagram_step_position_conflicts( $step_ID, 3, 1 );
           
// Call next steps recursively:
           
$this->set_diagram_default_step_position( $step_ID );
            break;
        }

       
// Find all steps which are not linked with other steps:
       
$unlinked_row = 1;
       
$unlinked_steps = array();
        foreach(
$this->diagram_steps as $step_ID => $step )
        {
            if( isset(
$step['xy'] ) )
            {    
// Step is linked:
               
if( $unlinked_row < $step['xy'][1] )
                {    
// Define last row:
                   
$unlinked_row = $step['xy'][1];
                }
            }
            else
            {    
// Step is not linked:
               
$unlinked_steps[] = $step_ID;
            }
        }
       
// Set position for unlinked steps at the end of diagram:
       
$unlinked_col = 1;
       
$unlinked_row++;
        foreach(
$unlinked_steps as $unlinked_step_ID )
        {
            if( isset(
$this->diagram_steps[ $unlinked_step_ID ]['xy'] ) )
            {    
// Skip step with already defined position:
               
continue;
            }
           
$this->resolve_diagram_step_position_conflicts( $unlinked_step_ID, $unlinked_col, $unlinked_row );
           
// Call next steps recursively:
           
$this->set_diagram_default_step_position( $unlinked_step_ID );
           
// Set next column:
           
$unlinked_col++;
            if(
$unlinked_col > 5 )
            {    
// Switch to next row:
               
$unlinked_col = 1;
               
$unlinked_row++;
            }
        }

        foreach(
$this->diagram_steps as $step_ID => $step )
        {    
// Convert row and column to CSS coordinates:
           
if( ! isset( $step['attrs']['style'] ) )
            {
               
$x = ( ( 19 * ( $step['xy'][0] - 1 ) ) + 4 ).'%';
               
$y = ( ( 250 * $step['xy'][1] ) - 150 ).'px';
               
$this->diagram_steps[ $step_ID ]['attrs']['style'] = 'left:'.$x.';top:'.$y;
            }
        }

       
// Update steps array with array with defined CSS coordinates:
       
$steps = $this->diagram_steps;

       
// Remove temp arrays:
       
unset( $this->diagram_steps );
        if( isset(
$this->diagram_cells ) )
        {
            unset(
$this->diagram_cells );
        }
    }


   
/**
     * Set default step positions for diagram view recursively
     *
     * @param integer Parent step ID
     */
   
function set_diagram_default_step_position( $parent_step_ID )
    {
        if( empty(
$this->diagram_steps[ $parent_step_ID ]['next_steps'] ) )
        {    
// No next steps, Finish branch:
           
return;
        }

       
// Count how many next steps current step has without defined position:
       
$new_next_step_IDs = array();
        foreach(
$this->diagram_steps[ $parent_step_ID ]['next_steps'] as $next_result => $next_step_ID )
        {
            if( isset(
$this->diagram_steps[ $next_step_ID ] ) &&
                ! isset(
$this->diagram_steps[ $next_step_ID ]['xy'] ) &&
               
$parent_step_ID != $next_step_ID &&
                !
in_array( $next_step_ID, $new_next_step_IDs ) )
            {    
// Exclude next steps with already defined position:
                //unset( $next_step_IDs[ $next_result ] );
               
$new_next_step_IDs[ $next_result ] = $next_step_ID;
            }
        }
       
$new_next_steps_count = count( $new_next_step_IDs );

        if(
$new_next_steps_count == 0 )
        {    
// No next steps without defined position, Finish branch:
           
return;
        }

       
// Get column and row of parent step:
       
list( $parent_col, $parent_row ) = $this->diagram_steps[ $parent_step_ID ]['xy'];

       
// Use next row after previous step box:
       
$current_row = $parent_row + 1;
       
// Set column for first next step:
       
if( $new_next_steps_count == 3 && $parent_col == 5 )
        {    
// If parent step is located in last column we should start first next step in shifted to 2 columns to the left:
           
$current_col = $parent_col - 2;
        }
        elseif(
$new_next_steps_count == 1 )
        {    
// Use same column as previous step box for single new next step
           
$current_col = $parent_col;
        }
        else
        {    
// Shift to the left for 2 or 3 new next steps:
           
$current_col = $parent_col - 1;
        }

        foreach(
$new_next_step_IDs as $next_result => $next_step_ID )
        {
            if(
$current_col < 1 )
            {    
// Min column is 1:
               
$current_col = 1;
            }
            elseif(
$current_col > 5 )
            {    
// Max column is 5:
               
$current_col = 5;
            }

           
// Define row and column:
           
$this->resolve_diagram_step_position_conflicts( $next_step_ID, $current_col, $current_row );

            if(
$new_next_steps_count == 1 )
            {    
// Don't calculate column for next steps because of single next step:
               
break;
            }

           
// Set column for next step:
           
$current_col += ( $new_next_steps_count == 3 || ( $new_next_steps_count == 2 && $parent_col == 1 ) ? 1 : 2 );
        }

       
// Note: we should run recursive function only after defined position for all next steps of current parent step:
       
foreach( $new_next_step_IDs as $next_result => $next_step_ID )
        {    
// Set positions to next step recursively:
           
$this->set_diagram_default_step_position( $next_step_ID );
        }
    }


   
/**
     * Set position column and row and resolve a conflict if another step is using same position
     *
     * @param integer Step ID
     * @param integer Column (1,2,3,4,5)
     * @param integer Row from 1 to infinity
     */
   
function resolve_diagram_step_position_conflicts( $step_ID, $col, $row )
    {
        if( ! isset(
$this->diagram_cells ) )
        {    
// Initialize array to store the filled cells on diagram:
           
$this->diagram_cells = array();
        }

        if( isset(
$this->diagram_cells[ $col.':'.$row ] ) )
        {    
// If current cell is filled with another step box we should resolve this conflict:
           
$conflict_is_resolved = false;

            while( !
$conflict_is_resolved )
            {
               
// LEFT shifting:
               
if( $col > 1 )
                {    
// Find a free cell in the left columns of the same row:
                   
for( $c = $col - 1; $c >= 1; $c-- )
                    {
                        if( ! isset(
$this->diagram_cells[ $c.':'.$row ] ) )
                        {    
// This is a free cell:
                           
for( $c2 = $c; $c2 <= $col - 1; $c2++ )
                            {    
// Move all step boxes before current to the left:
                               
$shifted_step_ID = $this->diagram_cells[ ( $c2 + 1 ).':'.$row ];
                               
$this->diagram_steps[ $shifted_step_ID ]['xy'] = array( $c2, $row );
                               
$this->diagram_cells[ $c2.':'.$row ] = $shifted_step_ID;
                            }
                           
// A free left cell is found, Stop to find others:
                           
$conflict_is_resolved = true;
                            break;
                        }
                    }
                }

               
// RIGHT shifting:
               
if( ! $conflict_is_resolved && $col < 5 )
                {    
// Find a free cell in the right columns of the same row:
                   
for( $c = $col + 1; $c <= 5; $c++ )
                    {
                        if( ! isset(
$this->diagram_cells[ $c.':'.$row ] ) )
                        {    
// This is a free cell, Use it for current step:
                           
$col = $c;
                           
// A free left cell is found, Stop to find others:
                           
$conflict_is_resolved = true;
                            break;
                        }
                    }
                }

               
// DOWN shifting:
               
if( ! $conflict_is_resolved )
                {    
// Find a free cell in the down rows of the same column:
                   
$row++;
                    if( ! isset(
$this->diagram_cells[ $col.':'.$row ] ) )
                    {    
// This is a free cell, Use it for current step:
                        // A free left cell is found, Stop to find others:
                       
$conflict_is_resolved = true;
                    }
                }
            }
        }

       
// Set the resolved column and for for current step:
       
$this->diagram_steps[ $step_ID ]['xy'] = array( $col, $row );

       
// Fill cells array to know current cell is busy with current step:
       
$this->diagram_cells[ $col.':'.$row ] = $step_ID;
    }


   
/**
     * Get first automation step
     *
     * @return object First automation step
     */
   
function & get_first_AutomationStep()
    {
        if( ! isset(
$this->first_AutomationStep ) )
        {    
// Load
           
global $DB;
           
$first_step_SQL = new SQL( 'Get first step of automation #'.$this->ID );
           
$first_step_SQL->SELECT( 'step_ID' );
           
$first_step_SQL->FROM( 'T_automation__step' );
           
$first_step_SQL->WHERE( 'step_autm_ID = '.$this->ID );
           
$first_step_SQL->ORDER_BY( 'step_order ASC' );
           
$first_step_SQL->LIMIT( 1 );
           
$first_step_ID = $DB->get_var( $first_step_SQL );
           
$AutomationStepCache = & get_AutomationStepCache();
           
$this->first_AutomationStep = & $AutomationStepCache->get_by_ID( $first_step_ID, false, false );
        }

        return
$this->first_AutomationStep;
    }


   
/**
     * Execute action for first step(s)
     *
     * @param integer User ID
     * @return string|boolean A process log, FALSE - if no first step
     */
   
function execute_first_step( $user_ID )
    {
        if( ! (
$first_AutomationStep = & $this->get_first_AutomationStep() ) )
        {    
// No step:
           
return false;
        }

        if(
$this->get( 'status' ) != 'active' )
        {    
// Don't execute a paused automation:
           
return false;
        }

       
// Execute first step(s) of this automation:
       
return $first_AutomationStep->execute_action( $user_ID );
    }
}

?>