<?php 
/*!
 * Jetpack CRM
 * https://jetpackcrm.com
 * V3.0+
 *
 * Copyright 2020 Automattic
 *
 * Date: 14/01/19
 */

use Automattic\JetpackCRM\Segment_Condition_Exception;

defined( 'ZEROBSCRM_PATH' ) || exit;

/**
* ZBS DAL >> Segments
*
* @author   Woody Hayday <hello@jetpackcrm.com>
* @version  2.0
* @access   public
* @see      https://jetpackcrm.com/kb
*/
class zbsDAL_segments extends zbsDAL_ObjectLayer {

    protected $objectType = ZBS_TYPE_SEGMENT;
    protected $objectModel = array(

        // ID
        'ID' => array('fieldname' => 'ID', 'format' => 'int'),

        // site + team generics
        'zbs_site' => array('fieldname' => 'zbs_site', 'format' => 'int'),
        'zbs_team' => array('fieldname' => 'zbs_team', 'format' => 'int'),
        'zbs_owner' => array('fieldname' => 'zbs_owner', 'format' => 'int'),

        // other fields
        'name' => array('fieldname' => 'zbsseg_name', 'format' => 'str'),
        'slug' => array('fieldname' => 'zbsseg_slug', 'format' => 'str'),
        'matchtype' => array('fieldname' => 'zbsseg_matchtype', 'format' => 'str'),
        'created' => array('fieldname' => 'zbsseg_created', 'format' => 'uts'),
        'lastupdated' => array('fieldname' => 'zbsseg_lastupdated', 'format' => 'uts'),
        'compilecount' => array('fieldname' => 'zbsseg_compilecount', 'format' => 'int'),
        'lastcompiled' => array('fieldname' => 'zbsseg_lastcompiled', 'format' => 'uts'),

        );


    function __construct($args=array()) {

        #} =========== LOAD ARGS ==============
        $defaultArgs = array(

            //'tag' => false,

        ); foreach ($defaultArgs as $argK => $argV){ $this->$argK = $argV; if (is_array($args) && isset($args[$argK])) {  if (is_array($args[$argK])){ $newData = $this->$argK; if (!is_array($newData)) $newData = array(); foreach ($args[$argK] as $subK => $subV){ $newData[$subK] = $subV; }$this->$argK = $newData;} else { $this->$argK = $args[$argK]; } } }
        #} =========== / LOAD ARGS =============


    }


    // ===============================================================================
    // ===========   SEGMENTS  =======================================================

    // generic get Company (by ID)
    // Super simplistic wrapper used by edit page etc. (generically called via dal->contacts->getSingle etc.)
    public function getSingle($ID=-1){

        return $this->getSegment($ID);

    }
    
    // This was actually written pre DAL2 and so still has some legacy layout of func 
    // etc. To be slowly refined if needed.
    
           
   /**
     * get a segment (header line)
     */
    public function getSegment($segmentID=-1,$withConditions=false,$checkOwnershipID=false){

        if ($segmentID > 0){
    
            global $ZBSCRM_t,$wpdb;

            $additionalWHERE = ''; $queryVars = array($segmentID);

            // check ownership
            // THIS ShoULD BE STANDARDISED THROUGHOUT DAL (ON DB2)
                // $checkOwnershipID = ID = check against that ID
                // $checkOwnershipID = true = check against get_current_user_id
                // $checkOwnershipID = false = do not check
            
            if ($checkOwnershipID === true){

                $segmentOwner = get_current_user_id();

            } elseif ($checkOwnershipID > 0){

                $segmentOwner = (int)$checkOwnershipID;

            } // else is false, don't test

            if (isset($segmentOwner)){

                // add check
                $additionalWHERE = 'AND zbs_owner = %d';
                $queryVars[] = $segmentOwner;

            }
            

            $potentialSegment = $wpdb->get_row( $this->prepare("SELECT * FROM ".$ZBSCRM_t['segments']." WHERE ID = %d ".$additionalWHERE."ORDER BY ID ASC LIMIT 0,1",$queryVars), OBJECT );

            if (isset($potentialSegment) && isset($potentialSegment->ID)){

                #} Retrieved :) fill + return
                
                    // tidy
                    $segment = $this->tidy_segment($potentialSegment);

                    if ($withConditions) {

                        $segment['conditions'] = $this->getSegmentConditions($segment['id']);

                    }

                    // this catches any 'broken' state segments.
                    // ... for now this is done via a setting, later we should build an error stack via DAL #refined-error-stack
                    $error_string = $this->segment_error( $segment['id'] );
                    if ( !empty( $error_string ) ){
                        $segment['error'] = zeroBSCRM_textExpose( $error_string );
                    }

                return $segment;
            }

        }

        return false;
    
   }

     /**
     * get Sements Pass -1 for $perPage and $page and this'll return ALL
     */
    public function getSegments($ownerID=-1,$perPage=10,$page=0,$withConditions=false,$searchPhrase='',$inArr='',$sortByField='',$sortOrder='DESC'){

                global $zbs,$ZBSCRM_t,$wpdb;

                $segments = false;

                // build query
                $sql = "SELECT * FROM ".$ZBSCRM_t['segments'];
                $wheres = array();
                $params = array();
                $orderByStr = '';

                    // Owner

                        // escape (all)
                        if ($ownerID != -99){

                            if ($ownerID === -1) $ownerID = get_current_user_id();

                            if (!empty($ownerID)) $wheres['zbs_owner'] = array('=',$ownerID,'%d');

                        }


                    // search phrase
                    if (!empty($searchPhrase)){

                        $wheres['zbsseg_name'] = array('LIKE','%'.$searchPhrase.'%','%s');

                    }

                    // in array
                    if (is_array($inArr) && count($inArr) > 0){

                        $wheres['ID'] = array('IN','('.implode(',', $inArr).')','%s');

                    }

                    // add where's to SQL
                    // + 
                    // feed in params
                    $whereStr = '';
                    if (count($wheres) > 0) foreach ($wheres as $key => $whereArr) {

                        if (!empty($whereStr)) 
                            $whereStr .= ' AND ';
                        else
                            $whereStr .= ' WHERE ';

                        // add in - NOTE: this is TRUSTING key + whereArr[0]
                        $whereStr .= $key.' '.$whereArr[0].' '.$whereArr[2];

                        // feed in params
                        $params[] = $whereArr[1];
                    }

                    // append to sql
                    $sql .= $whereStr;



                    // sort by
                    if (!empty($sortByField)){

                        if (!in_array($sortOrder, array('DESC','ASC'))) $sortOrder = 'DESC';

                        // parametise order field as is unchecked
                        //$orderByStr = ' ORDER BY %s '.$sortOrder;
                        //$params[] = $sortByField;
                        $orderByStr = ' ORDER BY '.$sortByField.' '.$sortOrder;

                    }


                    // pagination
                    if ($page == -1 && $perPage == -1){

                        // NO LIMITS :o


                    } else {

                        // Because SQL USING zero indexed page numbers, we remove -1 here
                        // ... DO NOT change this without seeing usage of the function (e.g. list view) - which'll break
                        $page = (int)$page-1;
                        if ($page < 0) $page = 0;

						// phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
						$orderByStr .= sprintf( ' LIMIT %d, %d ', (int) $page * (int) $perPage, (int) $perPage );
                    }

                    // append to sql
                    $sql .= $orderByStr;

                $query = $this->prepare($sql,$params);

                try {

                    $potentialSegments = $wpdb->get_results( $query, OBJECT );

                } catch (Exception $e){

                    // error with sql :/ for now nothin

                }

                if (isset($potentialSegments) && is_array($potentialSegments)) $segments = $potentialSegments;

                // TIDY
                $res = array();
                if (count($segments) > 0) foreach ($segments as $segment) {

                    // tidy
                    $resArr = $this->tidy_segment($segment);

                    // TO ADD to query / here withConditions
                    // TODO: REFACTOR into query? More efficient?
                    if ($withConditions) $resArr['conditions'] = $this->getSegmentConditions($segment->ID);

                    // this catches any 'broken' state segments.
                    // ... for now this is done via a setting, later we should build an error stack via DAL #refined-error-stack
                    $error_string = $this->segment_error( $segment->ID );
                    if ( !empty( $error_string ) ){
                        $resArr['error'] = zeroBSCRM_textExpose( $error_string );
                    }
                                
                    $res[] = $resArr;

                }

                return $res;
            
           }

           // brutal simple temp func (should be a wrapper really. segments to tidy up post DAL2 other obj)
           public function getSegmentCount(){

                global $ZBSCRM_t,$wpdb;

                // build query
                $sql = "SELECT COUNT(ID) FROM ".$ZBSCRM_t['segments'];

                return $wpdb->get_var($sql);
            
           }


             /**
             * deletes a Segment object (and its conditions)
             *
             * @param array $args Associative array of arguments
             *              id
             *
             * @return int success;
             */
            public function deleteSegment($args=array()){

                global $ZBSCRM_t, $wpdb, $zbs;

                #} ============ LOAD ARGS =============
                $defaultArgs = array(

                    'id'            => -1,
                    'saveOrphans'   => -1

                ); foreach ($defaultArgs as $argK => $argV){ $$argK = $argV; if (is_array($args) && isset($args[$argK])) {  if (is_array($args[$argK])){ $newData = $$argK; if (!is_array($newData)) $newData = array(); foreach ($args[$argK] as $subK => $subV){ $newData[$subK] = $subV; }$$argK = $newData;} else { $$argK = $args[$argK]; } } }
                #} =========== / LOAD ARGS ============

                #} Check ID & Delete :)
                $id = (int)$id;
                if (!empty($id) && $id > 0) {

                    $segment = $this->getSegment( $id );

                    $deleted = zeroBSCRM_db2_deleteGeneric($id,'segments');

                        // delete segment conditions?
                        // check $deleted?

                        $del = $wpdb->delete( 
                                    $ZBSCRM_t['segmentsconditions'], 
                                    array( // where
                                        'zbscondition_segmentid' => $id
                                        ),
                                    array(
                                        '%d'
                                        )
                                    );

                        #} Add to automator
                        zeroBSCRM_FireInternalAutomator('segment.delete',array(
                            'id'            => $id,
                            'saveOrphans'   => $saveOrphans,
                        ));

                        return $del;

                }

                return false;

            }

             /**
             * tidys a segment
             */
            public function tidy_segment($obj=false){

                $res = false;


                if (isset($obj->ID)){
                    $res = array();
                    $res['id'] = $obj->ID;
                    
                    $res['name'] = $obj->zbsseg_name;
                    $res['slug'] = $obj->zbsseg_slug;
                    $res['matchtype'] = $obj->zbsseg_matchtype;

                    $res['created'] = $obj->zbsseg_created;
                    $res['lastupdated'] = $obj->zbsseg_lastupdated;
                    $res['compilecount'] = $obj->zbsseg_compilecount;
                    $res['lastcompiled'] = $obj->zbsseg_lastcompiled;

                    // pretty date outputs for list viw
                    $res['createddate'] = zeroBSCRM_locale_utsToDate($obj->zbsseg_created);
                    $res['lastcompileddate'] = zeroBSCRM_locale_utsToDate($obj->zbsseg_lastcompiled);
                } 

                return $res;

           }

             /**
             * tidys a segment condition
             */
            public function tidy_segment_condition($obj=false){

                $res = false;

                if (isset($obj->ID)){
                    $res = array();
                    $res['id'] = $obj->ID;
                    
                    $res['segmentID'] = $obj->zbscondition_segmentid;
                    $res['type'] = $obj->zbscondition_type;
                    $res['operator'] = $obj->zbscondition_op;
                    $res['value'] = zeroBSCRM_textExpose($obj->zbscondition_val);
                    $res['value2'] = zeroBSCRM_textExpose($obj->zbscondition_val_secondary);

                    // applies any necessary conversions e.g. uts -> date
                    $res['valueconv'] = zeroBSCRM_segments_typeConversions($res['value'],$res['type'],$res['operator'],'out');
                    $res['value2conv'] = zeroBSCRM_segments_typeConversions($res['value2'],$res['type'],$res['operator'],'out');

                } 

                return $res;

           }

           
           /**
             * This is designed to mimic zeroBS_getSegments, but only to return a total count :) 
             */
            public function getSegmentsCountIncParams($ownerID=-1,$perPage=10,$page=0,$withConditions=false,$searchPhrase='',$inArr='',$sortByField='',$sortOrder='DESC'){

                global $zbs,$ZBSCRM_t,$wpdb;

                $segmentCount = false;

                // build query
                $sql = "SELECT COUNT(ID) segcount FROM ".$ZBSCRM_t['segments'];
                $wheres = array();
                $params = array();
                $orderByStr = '';

                    // Owner

                        // escape (all)
                        if ($ownerID != -99){

                            if ($ownerID === -1) $ownerID = get_current_user_id();

                            if (!empty($ownerID)) $wheres['zbs_owner'] = array('=',$ownerID,'%d');

                        }


                    // search phrase
                    if (!empty($searchPhrase)){

                        $wheres['zbsseg_name'] = array('LIKE',$searchPhrase,'%s');

                    }

                    // in array
                    if (is_array($inArr) && count($inArr) > 0){

                        $wheres['ID'] = array('IN','('.implode(',', $inArr).')','%s');

                    }

                    // add where's to SQL
                    // + 
                    // feed in params
                    $whereStr = '';
                    if (count($wheres) > 0) foreach ($wheres as $key => $whereArr) {

                        if (!empty($whereStr)) 
                            $whereStr .= ' AND ';
                        else
                            $whereStr .= ' WHERE ';

                        // add in - NOTE: this is TRUSTING key + whereArr[0]
                        $whereStr .= $key.' '.$whereArr[0].' '.$whereArr[2];

                        // feed in params
                        $params[] = $whereArr[1];
                    }

                    // append to sql
                    $sql .= $whereStr;

                $query = $this->prepare($sql,$params);

                try {

                    $potentialSegmentCount = $wpdb->get_row( $query, OBJECT );

                } catch (Exception $e){

                    // error with sql :/ for now nothin

                }

                if (isset($potentialSegmentCount) && isset($potentialSegmentCount->segcount)) $segmentCount = $potentialSegmentCount->segcount;

                return $segmentCount;
            
           }


           /**
             * builds a preview (top 5 + count) of a set of conditions which could be against a segment
             * expects a filtered list of conditions (e.g. zeroBSCRM_segments_filterConditions if sent through POST)
             *
             * Throws @Segment_Condition_Exception
             */
            public function previewSegment($conditions=array(),$matchType='all',$countOnly=false){

                    
                    // retrieve getContacts arguments from a list of segment conditions
                    $contactGetArgs = $this->segmentConditionsToArgs($conditions,$matchType);

                    // add top 5 + count params
                    $contactGetArgs['sortByField'] = 'RAND()';
                    $contactGetArgs['sortOrder'] = '';
                    $contactGetArgs['page'] = 0;
                    $contactGetArgs['perPage'] = 5;
                    $contactGetArgs['ignoreowner'] = zeroBSCRM_DAL2_ignoreOwnership(ZBS_TYPE_CONTACT);

                    // count ver
                    $countContactGetArgs = $contactGetArgs;
                    $countContactGetArgs['perPage'] = 100000;
                    $countContactGetArgs['count'] = true;

                    // count only
                    if ( $countOnly ) {
                        return $this->DAL()->contacts->getContacts($countContactGetArgs);
                    }

                    // Retrieve
                    return array(
                        // DEBUG 
                        //'conditions' => $conditions, // TEMP - remove this
                        //'args' => $contactGetArgs, // TEMP - remove this
                        'count'=>$this->DAL()->contacts->getContacts($countContactGetArgs),
                        'list'=>$this->DAL()->contacts->getContacts($contactGetArgs)
                    );

           }


           /**
             * used by previewSegment and getSegmentAudience to build condition args
             */
           public function segmentConditionsToArgs($conditions=array(),$matchType='all'){

                if (is_array($conditions) && count($conditions) > 0){

                    $contactGetArgs = array();
                    $conditionIndx = 0; // this allows multiple queries for SAME field (e.g. status = x or status = y)

                    // cycle through & add to contact request arr
                    foreach ($conditions as $condition){

                        $newArgs = $this->segmentConditionArgs($condition,$conditionIndx); $additionalWHERE = false;

                        // legit? merge (must be recursive)
                        if (is_array($newArgs)) $contactGetArgs = array_merge_recursive($contactGetArgs,$newArgs);

                        $conditionIndx++;

                    }

                    // match type ALL is default, this switches to ANY
                    if ($matchType == 'one') $contactGetArgs['whereCase'] = 'OR';

                    return $contactGetArgs;                            

                }

                return array();

           }
           
           /**
             * get a segment (header line)
             */
            public function getSegmentBySlug($segmentSlug=-1,$withConditions=false,$checkOwnershipID=false){

                if (!empty($segmentSlug)){
            
                    global $ZBSCRM_t,$wpdb;

                    $additionalWHERE = ''; $queryVars = array($segmentSlug);

                    // check ownership
                    // THIS ShoULD BE STANDARDISED THROUGHOUT DAL (ON DB2)
                        // $checkOwnershipID = ID = check against that ID
                        // $checkOwnershipID = true = check against get_current_user_id
                        // $checkOwnershipID = false = do not check
                    
                    if ($checkOwnershipID === true){

                        $segmentOwner = get_current_user_id();

                    } elseif ($checkOwnershipID > 0){

                        $segmentOwner = (int)$checkOwnershipID;

                    } // else is false, don't test

                    if (isset($segmentOwner)){

                        // add check
                        $additionalWHERE = 'AND zbs_owner = %d';
                        $queryVars[] = $segmentOwner;

                    }
                    

                    $potentialSegment = $wpdb->get_row( $this->prepare("SELECT * FROM ".$ZBSCRM_t['segments']." WHERE zbsseg_slug = %s ".$additionalWHERE."ORDER BY ID ASC LIMIT 0,1",$queryVars), OBJECT );

                    if (isset($potentialSegment) && isset($potentialSegment->ID)){

                        #} Retrieved :) fill + return
                        
                            // tidy
                            $segment = $this->tidy_segment($potentialSegment);

                            if ($withConditions) {

                                $segment['conditions'] = $this->getSegmentConditions($segment['id']);

                            }


                        return $segment;
                    }

                }

                return false;

            }

            /*
            * Compatibility for typo:
            * #backward-compatibility
            */
            public function getSegementAudience($segmentID=-1,$page=0,$perPage=20,$sortByField='ID',$sortOrder='DESC',$onlyCount=false,$withDND=false){
                return $this->getSegmentAudience( $segmentID, $page, $perPage, $sortByField, $sortOrder, $onlyCount, $withDND );
            }

	/**
	 * Runs a filtered search on customers based on a segment's condition
	 *
	 * @param int $segment_id ID of segment.
	 * @param int $page Page number.
	 * @param int $per_page Number of objects per page.
	 * @param int $sort_by_field Field to sort by.
	 * @param int $sort_order Sort order.
	 * @param int $only_count Only return counts.
	 * @param int $with_dnd Return DND info.
	 * @param int $limited_fields Only return specific fields.
	 *
	 * @return array or count ($onlyCount)
	 */
	public function getSegmentAudience(
		$segment_id = -1,
		$page = 0,
		$per_page = 20,
		$sort_by_field = 'ID',
		$sort_order = 'DESC',
		$only_count = false,
		$with_dnd = false,
		$limited_fields = false
	) {

		// assumes sensible paging + sort vars... no checking of them

		if ( $segment_id > 0 ) {

			#} Retrieve segment + conditions
			$segment = $this->getSegment( $segment_id, true );

			$conditions = array();
			if ( isset( $segment['conditions'] ) ) {
				$conditions = $segment['conditions'];
			}

			$match_type = 'all';
			if ( isset( $segment['matchtype'] ) ) {
				$match_type = $segment['matchtype'];
			}

			try {

				// retrieve getContacts arguments from a list of segment conditions
				$contact_get_args = $this->segmentConditionsToArgs( $conditions, $match_type );

				// Remove any segment area error notice
				$this->remove_segment_error( $segment_id );

			} catch ( Segment_Condition_Exception $exception ) {

				// We're missing the condition class for one or more of this segment's conditions.
				$this->segment_error_condition_missing( $segment_id, $exception );

				// return fail
				return false;

			}

			// needs to be ownerless for now
			$contact_get_args['ignoreowner'] = zeroBSCRM_DAL2_ignoreOwnership( ZBS_TYPE_CONTACT );

			// add paging params
			$contact_get_args['sortByField'] = $sort_by_field;
			$contact_get_args['sortOrder']   = $sort_order;
			$contact_get_args['page']        = $page;

			if ( $per_page !== -1 ) {
				$contact_get_args['perPage'] = $per_page; // over 100k? :o
			} else {
				// no limits
				$contact_get_args['page']    = -1;
				$contact_get_args['perPage'] = -1;
			}

			// count ver
			if ( $only_count ) {
				$contact_get_args['page']    = -1;
				$contact_get_args['perPage'] = -1;
				$contact_get_args['count']   = true;

				$count = $this->DAL()->contacts->getContacts( $contact_get_args );

				// effectively a compile, so update compiled no on record
				$this->updateSegmentCompiled( $segment_id, $count, time() );

				return $count;
			}

			// got dnd?
			if ( $with_dnd ) {
				$contact_get_args['withDND'] = true;
			}

			// limited fields?
			if ( is_array( $limited_fields ) ) {
				$contact_get_args['onlyColumns'] = $limited_fields;
			}

			$contact_get_args['withAssigned'] = true;

			$contacts = $this->DAL()->contacts->getContacts( $contact_get_args );

			// if no limits, update compile record (effectively a compile)
			if ( $contact_get_args['page'] === -1 && $contact_get_args['perPage'] === -1 ) {

				$this->updateSegmentCompiled( $segment_id, count( $contacts ), time() );

			}

			// Retrieve
			return $contacts;

		}

		return false;
	}

           /**
             * checks all segments against a contact
             */
            public function getSegmentsContainingContact($contactID=-1,$justIDs=false){

                $ret = array();

                if ($contactID > 0){

                    // get all segments
                    $segments = $this->getSegments(-1,1000,0,true);

                    if (count($segments) > 0) {

                        foreach ($segments as $segment){

                            // pass obj to check (saves it querying)
                            if ($this->isContactInSegment($contactID, $segment['id'],$segment)){

                                // is in segment
                                if ($justIDs)
                                    $ret[] = $segment['id'];
                                else
                                    $ret[] = $segment;

                            }

                        } // foreach segment

                    } // if segments

                } // if contact id

                return $ret;

           }

           /**
             * Checks if a contact matches segment conditions
             * ... can pass $segmentObj to avoid queries (performance) if already have it
             */
            public function isContactInSegment($contactID=-1,$segmentID=-1,$segmentObj=false){

                if ($segmentID > 0 && $contactID > 0){

                    #} Retrieve segment + conditions
                    if (is_array($segmentObj)) 
                        $segment = $segmentObj;
                    else
                        $segment = $this->getSegment($segmentID,true);

                    #} Set these
                    $conditions = array(); if (isset($segment['conditions'])) $conditions = $segment['conditions'];
                    $matchType = 'all'; if (isset($segment['matchtype'])) $matchType = $segment['matchtype'];

                        try {

                            // retrieve getContacts arguments from a list of segment conditions
                            $contactGetArgs = $this->segmentConditionsToArgs($conditions,$matchType);

                        } catch ( Segment_Condition_Exception $exception ){

                            // We're missing the condition class for one or more of this segment's conditions.
                            $this->segment_error_condition_missing( $segmentID, $exception );

                            // return false, because of the segment error, we cannot be sure.
                            return false;
                            
                        }

                        // add paging params
                        $contactGetArgs['page'] = -1;
                        $contactGetArgs['perPage'] = -1;
                        $contactGetArgs['count'] = true;

                        // add id check (via rough additionalWhere)
                        if (!isset($contactGetArgs['additionalWhereArr'])) $contactGetArgs['additionalWhereArr'] = array();
                        $contactGetArgs['additionalWhereArr']['idCheck'] = array("ID",'=','%d',$contactID);

                        // should only ever be 1 or 0
                        $count = $this->DAL()->contacts->getContacts($contactGetArgs);

                        if ($count == 1) 
                            return true;

                        // nope.
                        return false;

                }

                return false;

           }

           /**
             * Compiles all segments             
             */
            public function compile_all_segments(){

                // get all segments
                $segments = $this->getSegments(-1,1000,0,true);

                if (count($segments) > 0) foreach ($segments as $segment){

                        // compile this segment
                        $this->compileSegment( $segment['id'] );

                } // foreach segment

                return false;

           }

           /**
             * Compiles any segments which are affected by a single contact change
             * includeSegments is an array of id's - this allows you to pass 'what contact was in before' (because these need --1)
             */
            public function compileSegmentsAffectedByContact($contactID=-1,$includeSegments=array()){

                if ($contactID > 0){

                    // get all segments
                    $segments = $this->getSegments(-1,1000,0,true);

                    if (count($segments) > 0) foreach ($segments as $segment){

                        // pass obj to check (saves it querying)
                        if ($this->isContactInSegment($contactID, $segment['id'],$segment) || in_array($segment['id'], $includeSegments)){

                            // is in segment

                            // compile this segment
                            $this->compileSegment($segment['id']);

                        }

                    } // foreach segment

                } // if contact id

                return false;

           }

            /*
             * Compiles any segments which are affected by a quote change
             *
             * This means segments with conditions of type:
             * quotecount
             *
             * Here we accept a @param of @quote to allow future refinements where by exact conditions are compared.
             */
            public function compileSegmentsAffectedByQuote( $quote = array() ){

                if ( is_array( $quote ) ){

                    // get all segments
                    $segments = $this->getSegments(-1,1000,0,true);

                    if (count($segments) > 0) foreach ($segments as $segment){

                        // does this segment have a condition we're watching for
                        if ( $this->segmentHasConditionType( $segment, 
                            array(
                                'quotecount'
                            )
                        ) ){

                            // recompile the segment
                            $this->compileSegment($segment['id']);

                        }

                    } // foreach segment

                } // if contact id

                return false;

            }

            /*
             * Compiles any segments which are affected by an invoice change
             *
             * This means segments with conditions of type:
             * invcount
             * successtotaltransval
             *
             * Here we accept a @param of @invoice to allow future refinements where by exact conditions are compared.
             */
            public function compileSegmentsAffectedByInvoice( $invoice = array() ){

                if ( is_array( $invoice ) ){

                    // get all segments
                    $segments = $this->getSegments(-1,1000,0,true);

                    if (count($segments) > 0) foreach ($segments as $segment){

                        // does this segment have a condition we're watching for
                        if ( $this->segmentHasConditionType( $segment, 
                            array(
                                'invcount',
                                'successtotaltransval'
                            )
                        ) ){

                            // recompile the segment
                            $this->compileSegment($segment['id']);

                        }

                    } // foreach segment

                } // if contact id

                return false;

            }

            /*
             * Compiles any segments which are affected by a transaction change
             *
             * This means segments with conditions of type:
             * trancount
             * successsingletransval
             * successtotaltransval
             * successtransref
             * successtransname
             * successtransstr
             *
             * Here we accept a @param of @transaction to allow future refinements where by exact conditions are compared.
             *
             */
            public function compileSegmentsAffectedByTransaction( $transaction = array() ){

                if ( is_array( $transaction ) ){

                    // get all segments
                    $segments = $this->getSegments(-1,1000,0,true);

                    if (count($segments) > 0) foreach ($segments as $segment){

                        // does this segment have a condition we're watching for
                        if ( $this->segmentHasConditionType( $segment, 
                            array(
                                'trancount',
                                'successsingletransval',
                                'successtotaltransval',
                                'successtransref',
                                'successtransname',
                                'successtransstr'
                            )
                        ) ){

                            // recompile the segment
                            $this->compileSegment($segment['id']);

                        }

                    } // foreach segment

                } // if contact id

                return false;

            }


           
           /**
             * 
             */
            public function getSegmentConditions($segmentID=-1){

                if ($segmentID > 0){

                    global $ZBSCRM_t,$wpdb;

                    $potentialSegmentConditions = $wpdb->get_results( $this->prepare("SELECT * FROM ".$ZBSCRM_t['segmentsconditions']." WHERE zbscondition_segmentid = %d",$segmentID) );

                    if (is_array($potentialSegmentConditions) && count($potentialSegmentConditions) > 0) {

                        $returnConditions = array();

                        foreach ($potentialSegmentConditions as $condition){

                            $returnConditions[] = $this->tidy_segment_condition($condition);

                        }


                        return $returnConditions;

                    }
                    

                }

                return false;
            
           }


           /**
             * Simple func to update the segment compiled count (says how many contacts currently in segment)
             */
           public function updateSegmentCompiled($segmentID=-1,$segmentCount=0,$compiledUTS=-1){
                
                global $ZBSCRM_t,$wpdb;

                if ($segmentID > 0){

                    // checks
                    $count = 0; if ($segmentCount > 0) $count = (int)$segmentCount;
                    $compiled = time(); if ($compiledUTS > 0) $compiled = (int)$compiledUTS;

                    if ($wpdb->update( 
                            $ZBSCRM_t['segments'], 
                            array( 
                                'zbsseg_compilecount' => $count,
                                'zbsseg_lastcompiled' => $compiled
                            ), 
                            array( // where
                                'ID' => $segmentID
                                ),
                            array( 
                                '%d', 
                                '%d'
                            ),
                            array(
                                '%d'
                                )
                            ) !== false){

                            // udpdated
                            return true;

                        } else {

                            // could not update?!
                            return false;

                        }


                }

           }

           /**
             * 
             */
            public function addUpdateSegment($segmentID=-1,$segmentOwner=-1,$segmentName='',$segmentConditions=array(),$segmentMatchType='all',$forceCompile=false){

                global $ZBSCRM_t,$wpdb;

                #} After ops, shall I compile audience?
                $toCompile = $forceCompile;

                if ($segmentID > 0){

                    #} Update a segment

                        #} Owner - if -1 then use current user
                        if ($segmentOwner <= 0) $segmentOwner = get_current_user_id();

                        #} Empty name = untitled
                        if (empty($segmentName)) $segmentName = __('Untitled Segment',"zero-bs-crm");

                        // slug auto-updates with name, (fix later if issue)
                        // in fact, just leave as whatever first set? (affects quickfilter URLs etc?)
                        // just did in end
                        #} Generate slug
                        $segmentSlug = $this->makeSlug($segmentName);

                        #} update header line
                        if ($wpdb->update( 
                            $ZBSCRM_t['segments'], 
                            array( 
                                'zbs_owner' => $segmentOwner,
                                'zbsseg_name' => $segmentName,
                                'zbsseg_slug' => $segmentSlug,
                                'zbsseg_matchtype' => $segmentMatchType,
                                'zbsseg_lastupdated' => time()
                            ), 
                            array( // where
                                'ID' => $segmentID
                                ),
                            array( 
                                '%d', 
                                '%s',
                                '%s',
                                '%s',
                                '%d'
                            ),
                            array(
                                '%d'
                                )
                            ) !== false){

                            // updated, move on..

                            // add segment conditions
                            $this->addUpdateSegmentConditions($segmentID,$segmentConditions);

                            // return id
                            $returnID = $segmentID;

                            // force to compile
                            $toCompile = true; $compileID = $segmentID;

                        } else {

                            // could not update?!
                            return false;

                        }
                    

                } else {

                    #} Add a new segment

                        #} Owner - if -1 then use current user
                        if ($segmentOwner <= 0) $segmentOwner = get_current_user_id();

                        #} Empty name = untitled (should never happen because of UI)
                        if (empty($segmentName)) $segmentName = __('Untitled Segment',"zero-bs-crm");

                        #} Generate slug
                        $segmentSlug = $this->makeSlug($segmentName);

                        #} Add header line
                        if ($wpdb->insert( 
                            $ZBSCRM_t['segments'], 
                            array( 
                                'zbs_owner' => $segmentOwner,
                                'zbsseg_name' => $segmentName,
                                'zbsseg_slug' => $segmentSlug,
                                'zbsseg_matchtype' => $segmentMatchType,
                                'zbsseg_created' => time(),
                                'zbsseg_lastupdated' => time(),
                                'zbsseg_lastcompiled' => time(), // we'll compile it shortly, set as now :)
                            ), 
                            array( 
                                '%d', 
                                '%s',
                                '%s',
                                '%s',
                                '%d',
                                '%d',
                                '%d'
                            ) 
                        ) > 0){

                            // inserted, let's move on
                            $newSegmentID = $wpdb->insert_id;

                            // add segment conditions
                            $this->addUpdateSegmentConditions($newSegmentID,$segmentConditions);

                            // force to compile
                            $toCompile = true; $compileID = $newSegmentID;

                            // return id
                            $returnID = $newSegmentID;

                        } else {

                            // could not insert?!
                            return false;

                        }

                } // / new

                // "compile" segments?
                if ($toCompile && !empty($compileID)){

                    // compiles + logs how many in segment against record
                    $totalInSegment = $this->compileSegment($compileID);

                }

                if (isset($returnID))
                    return $returnID;
                else
                    return false;
            
           }


           public function addUpdateSegmentConditions($segmentID=-1,$conditions=array()){

                if ($segmentID > 0 && is_array($conditions)){

                    // lazy - here I NUKE all existing conditions then readd...
                    $this->removeSegmentConditions($segmentID);

                        if (is_array($conditions) && count($conditions) > 0){

                            $retConditions = array();

                            foreach ($conditions as $sCondition){


                                $newConditionID = $this->addUpdateSegmentCondition(-1,$segmentID,$sCondition);

                                if (!empty($newConditionID)){

                                    // new condition added, insert
                                    $retConditions[$newConditionID] = $sCondition;

                                } else {

                                    // error inserting condition?!
                                    return false;

                                }

                            }

                            return $retConditions;

                        }


                } 

                return array();

           }

           /**
             * 
             */
            public function addUpdateSegmentCondition($conditionID=-1,$segmentID=-1,$conditionDetails=array()){

                global $ZBSCRM_t,$wpdb;

                #} Check/build empty condition details
                $condition = array(
                    'type' => '',
                    'operator' => '',
                    'val' => '',
                    'valsecondary' => ''
                );
                if (isset($conditionDetails['type'])) $condition['type'] = $conditionDetails['type'];
                if (isset($conditionDetails['value'])) $condition['val'] = $conditionDetails['value'];
                if (isset($conditionDetails['operator']) && $conditionDetails['operator'] !== -1) $condition['operator'] = $conditionDetails['operator'];
                if (isset($conditionDetails['value2'])) $condition['valsecondary'] = $conditionDetails['value2'];

                // update or insert?
                if ($conditionID > 0){

                    #} Update a segment condition

                        #} update line
                        if ($wpdb->update( 
                            $ZBSCRM_t['segmentsconditions'], 
                            array( 
                                'zbscondition_segmentid' => $segmentID,
                                'zbscondition_type' => $condition['type'],
                                'zbscondition_op' => $condition['operator'],
                                'zbscondition_val' => $condition['val'],
                                'zbscondition_val_secondary' => $condition['valsecondary']
                            ), 
                            array( // where
                                'ID' => $conditionID
                                ),
                            array( 
                                '%d', 
                                '%s',
                                '%s',
                                '%s',
                                '%s'
                            ),
                            array(
                                '%d'
                                )
                            ) !== false){

                            return $conditionID;

                        } else {

                            // could not update?!
                            return false;

                        }
                    

                } else {

                    #} Add a new segmentcondition


                        #} Add condition line
                        if ($wpdb->insert( 
                            $ZBSCRM_t['segmentsconditions'], 
                            array( 
                                'zbscondition_segmentid' => $segmentID,
                                'zbscondition_type' => $condition['type'],
                                'zbscondition_op' => $condition['operator'],
                                'zbscondition_val' => $condition['val'],
                                'zbscondition_val_secondary' => $condition['valsecondary']
                            ), 
                            array( 
                                '%d', 
                                '%s',
                                '%s',
                                '%s',
                                '%s'
                            ) 
                        ) > 0){


                            // inserted
                            return $wpdb->insert_id;

                        } else {

                            // could not insert?!
                            return false;

                        }

                } // / new

                return false;

            
           }


           /**
             *  Does the segment have a condition of type x?
             * 
             * @param array segment - segment obj as returned by getSegment(s)
             * @param array condition_type - array of condition types to check for
             */
            public function segmentHasConditionType( $segment = array(), $condition_types = array() ){

                // got a legit segment, and some conditions?
                if ( is_array( $segment ) && isset( $segment['conditions'] ) && is_array( $segment['conditions'] ) && is_array( $condition_types ) ){

                    // cycle through segment conditions & check
                    foreach ( $segment['conditions'] as $condition ){

                        if ( in_array( $condition['type'], $condition_types ) ) return true;

                    }


                }

                return false;
                
            }

           /**
             *  empty all conditions against seg
             */
            public function removeSegmentConditions($segmentID=-1){

                if (!empty($segmentID)) {

                    global $ZBSCRM_t,$wpdb;

                    return $wpdb->delete( 
                                $ZBSCRM_t['segmentsconditions'], 
                                array( // where
                                    'zbscondition_segmentid' => $segmentID
                                    ),
                                array(
                                    '%d'
                                    )
                                );

                }

                return false;
            
           }



           /**
             * Segment rules
             * takes a condition + returns a contact dal2 get arr param
             * 
             */
            public function segmentConditionArgs($condition=array(),$conditionKeySuffix=''){

                if (is_array($condition) && isset($condition['type']) && isset($condition['operator'])){

                    global $zbs,$wpdb,$ZBSCRM_t;

			if ( ! empty( $condition['type'] ) ) {
				// normalise type string
				$condition_type  = preg_replace( '/^zbsc_/', '', $condition['type'] );
				$filter_tag = $this->makeSlug( $condition_type ) . '_zbsSegmentArgumentBuild';

				$potential_args = apply_filters( $filter_tag, false, $condition, $conditionKeySuffix ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
				// got anything back?

				if ( $potential_args !== false ) {
					return $potential_args;
				}
			}

			switch ( $condition['type'] ) {

                        case 'status':
                        case 'zbsc_status':

                        /* while this is right, it doesn't allow for MULTIPLE status cond lines, so do via sql:
                            if ($condition['operator'] == 'equal')
                                return array('hasStatus'=>$condition['value']);
                            else
                                return array('otherStatus'=>$condition['value']);
                        */
                            if ($condition['operator'] == 'equal')
                                return array('additionalWhereArr'=>
                                            array('statusEqual'.$conditionKeySuffix=>array("zbsc_status",'=','%s',$condition['value']))
                                        );
                            else
                                return array('additionalWhereArr'=>
                                            array('statusEqual'.$conditionKeySuffix=>array("zbsc_status",'<>','%s',$condition['value']))
                                        );

                            break;

                        case 'fullname': // 'equal','notequal','contains'

                            if ($condition['operator'] == 'equal')
                                return array('additionalWhereArr'=>
                                            array('fullnameEqual'.$conditionKeySuffix=>array("CONCAT(zbsc_fname,' ',zbsc_lname)",'=','%s',$condition['value']))
                                        );
                            elseif ($condition['operator'] == 'notequal')
                                return array('additionalWhereArr'=>
                                            array('fullnameEqual'.$conditionKeySuffix=>array("CONCAT(zbsc_fname,' ',zbsc_lname)",'<>','%s',$condition['value']))
                                        );
                            elseif ($condition['operator'] == 'contains')
                                return array('additionalWhereArr'=>
                                            array('fullnameEqual'.$conditionKeySuffix=>array("CONCAT(zbsc_fname,' ',zbsc_lname)",'LIKE','%s','%'.$condition['value'].'%'))
                                        );
                            break;

                        case 'email':
                        case 'zbsc_email':

                            if ($condition['operator'] == 'equal'){
                                // while this is right, it doesn't allow for MULTIPLE status cond lines, so do via sql:
                                // return array('hasEmail'=>$condition['value']);
                                /* // this was good, but was effectively AND
                                return array('additionalWhereArr'=>
                                            array(
                                                'email'.$conditionKeySuffix=>array('zbsc_email','=','%s',$condition['value']),
                                                'emailAKA'.$conditionKeySuffix=>array('ID','IN',"(SELECT aka_id FROM ".$ZBSCRM_t['aka']." WHERE aka_type = ".ZBS_TYPE_CONTACT." AND aka_alias = %s)",$condition['value'])
                                                )
                                        );
                                */
                                // This was required to work with OR (e.g. postcode 1 = x or postcode 2 = x)
                                // -----------------------
                                // This generates a query like 'zbsc_fname LIKE %s OR zbsc_lname LIKE %s', 
                                // which we then need to include as direct subquery
                                /* THIS WORKS: but refactored below
                                $conditionQArr = $this->buildWheres(array(
                                                                    'email'.$conditionKeySuffix=>array('zbsc_email','=','%s',$condition['value']),
                                                                    'emailAKA'.$conditionKeySuffix=>array('ID','IN',"(SELECT aka_id FROM ".$ZBSCRM_t['aka']." WHERE aka_type = ".ZBS_TYPE_CONTACT." AND aka_alias = %s)",$condition['value'])
                                                                    ),'',array(),'OR',false);
                                if (is_array($conditionQArr) && isset($conditionQArr['where']) && !empty($conditionQArr['where'])){                                    
                                    return array('additionalWhereArr'=>array('direct'=>array(array('('.$conditionQArr['where'].')',$conditionQArr['params']))));
                                }
                                return array();
                                */
                                // this way for OR situations
                                return $this->segmentBuildDirectOrClause(array(
                                                                    'email'.$conditionKeySuffix=>array('zbsc_email','=','%s',$condition['value']),
                                                                    'emailAKA'.$conditionKeySuffix=>array('ID','IN',"(SELECT aka_id FROM ".$ZBSCRM_t['aka']." WHERE aka_type = ".ZBS_TYPE_CONTACT." AND aka_alias = %s)",$condition['value'])
                                                                    ),'OR');
                                // -----------------------
                            } elseif ($condition['operator'] == 'notequal')
                                return array('additionalWhereArr'=>
                                            array(
                                                'notEmail'.$conditionKeySuffix=>array('zbsc_email','<>','%s',$condition['value']),
                                                'notEmailAka'.$conditionKeySuffix=>array('ID','NOT IN',"(SELECT aka_id FROM ".$ZBSCRM_t['aka']." WHERE aka_type = ".ZBS_TYPE_CONTACT." AND aka_alias = %s)",$condition['value'])
                                                )
                                        );
                            elseif ($condition['operator'] == 'contains')
                                return array('additionalWhereArr'=>
                                            array('emailContains'.$conditionKeySuffix=>array("zbsc_email",'LIKE','%s','%'.$condition['value'].'%'))
                                        );

							break;

                        // TBA (When DAL2 trans etc.)
                        case 'totalval': // 'equal','notequal','larger','less','floatrange'

                            break;

                        case 'dateadded': // 'before','after','daterange','datetimerange','beforeequal','afterequal','previousdays'

                            // date added
                            if ($condition['operator'] == 'before')
                                return array('additionalWhereArr'=>
                                            array('olderThan'.$conditionKeySuffix=>array('zbsc_created','<','%d',$condition['value']))
                                        );
                            elseif ($condition['operator'] == 'beforeequal')
                                return array('additionalWhereArr'=>
                                            array( 'olderThanEqual' . $conditionKeySuffix => array( 'zbsc_created', '<=', '%d', $condition['value'] ) )
                                        );
                            elseif ($condition['operator'] == 'after')
                                return array('additionalWhereArr'=>
                                            array('newerThan'.$conditionKeySuffix=>array('zbsc_created','>','%d',$condition['value']))
                                        );
                            elseif ($condition['operator'] == 'afterequal')
                                return array('additionalWhereArr'=>
                                            array( 'newerThanEqual' . $conditionKeySuffix => array( 'zbsc_created', '>=', '%d', $condition['value'] ) )
                                        );
                            elseif (
                                        $condition['operator'] == 'daterange'
                                        ||
                                        $condition['operator'] == 'datetimerange'
                                    ){

                                $before = false; $after = false;
                                // split out the value 
                                if (isset($condition['value']) && !empty($condition['value'])) $after = (int)$condition['value'];
                                if (isset($condition['value2']) && !empty($condition['value2'])) $before = (int)$condition['value2'];

                                // while this is right, it doesn't allow for MULTIPLE status cond lines, so do via sql:
                                // return array('newerThan'=>$after,'olderThan'=>$before);
                                return array('additionalWhereArr'=>
                                            array(
                                                'newerThan' . $conditionKeySuffix => array( 'zbsc_created', '>=', '%d', $condition['value'] ),
                                                'olderThan' . $conditionKeySuffix => array( 'zbsc_created', '<=', '%d', $condition['value2'] )
                                            )
                                        );
                            } elseif ($condition['operator'] == 'previousdays'){

                                $days_value = (int)$condition['value'];
                                $midnight = strtotime( "midnight" );
                                $previous_days_uts = $midnight - ( ( 60 * 60 * 24 ) * $days_value );

                                return array('additionalWhereArr'=>
                                            array(
                                                'newerThanPreviousDays' . $conditionKeySuffix => array( 'zbsc_created', '<=', '%d', time() ),
                                                'olderThanPreviousDays' . $conditionKeySuffix => array( 'zbsc_created', '>=', '%d', $previous_days_uts )
                                            )
                                        );
                            }   

                            break;

                        case 'datelastcontacted': // 'before','after','daterange','datetimerange','beforeequal','afterequal','previousdays'

                            // contactedAfter
                            if ($condition['operator'] == 'before') // datetime
                                return array('additionalWhereArr'=>
                                            array('contactedBefore'.$conditionKeySuffix=>array('zbsc_lastcontacted','<','%d',$condition['value']))
                                        );
                            elseif ($condition['operator'] == 'beforeequal') // date
                                return array('additionalWhereArr'=>
                                            array( 'contactedBeforeEqual' . $conditionKeySuffix => array( 'zbsc_lastcontacted', '<=', '%d', $condition['value'] ) )
                                        );
                            elseif ($condition['operator'] == 'after') //datetime
                                return array('additionalWhereArr'=>
                                            array('contactedAfter'.$conditionKeySuffix=>array('zbsc_lastcontacted','>','%d',$condition['value']))
                                        );
                            elseif ($condition['operator'] == 'afterequal') // date
                                return array('additionalWhereArr'=>
                                            array( 'contactedAfterEqual' . $conditionKeySuffix => array( 'zbsc_lastcontacted', '>=', '%d', $condition['value'] ) )
                                        );
                            elseif (
                                        $condition['operator'] == 'daterange'
                                        ||
                                        $condition['operator'] == 'datetimerange'
                                    ){

                                $before = false; $after = false;
                                // split out the value 
                                if (isset($condition['value']) && !empty($condition['value'])) $after = (int)$condition['value'];
                                if (isset($condition['value2']) && !empty($condition['value2'])) $before = (int)$condition['value2'];

                                // while this is right, it doesn't allow for MULTIPLE status cond lines, so do via sql:
                                // return array('contactedAfter'=>$after,'contactedBefore'=>$before);
                                return array('additionalWhereArr'=>
                                            array(
                                                'contactedAfter'.$conditionKeySuffix=>array('zbsc_lastcontacted','>=','%d',$after),
                                                'contactedBefore'.$conditionKeySuffix=>array('zbsc_lastcontacted','<=','%d',$before)
                                            )
                                        );
                            } elseif ( $condition['operator'] == 'previousdays' ){

                                $days_value = (int)$condition['value'];
                                $previous_days_uts = strtotime( "-" . $days_value . " days" );

                                return array('additionalWhereArr'=>
                                            array(
                                                'contactedAfterPreviousDays' . $conditionKeySuffix => array( 'zbsc_lastcontacted', '<=', '%d', time() ),
                                                'contactedBeforePreviousDays' . $conditionKeySuffix => array( 'zbsc_lastcontacted', '>=', '%d', $previous_days_uts )
                                            )
                                        );
                            }

                            break;

                        case 'tagged': // 'tag'

                            // while this is right, it doesn't allow for MULTIPLE status cond lines, so do via sql:
                            // return array('isTagged'=>$condition['value']);
                            // NOTE
                            // ... this is a DIRECT query, so format for adding here is a little diff
                            // ... and only works (not overriding existing ['direct']) because the calling func of this func has to especially copy separately
                            return array('additionalWhereArr'=>
                                            array('direct' => array(
                                                array('(SELECT COUNT(ID) FROM '.$ZBSCRM_t['taglinks'].' WHERE zbstl_objtype = %d AND zbstl_objid = contact.ID AND zbstl_tagid = %d) > 0',array(ZBS_TYPE_CONTACT,$condition['value']))
                                                )
                                            )
                                        );                        

                            break;

                        case 'nottagged': // 'tag'

                            // while this is right, it doesn't allow for MULTIPLE status cond lines, so do via sql:
                            // return array('isNotTagged'=>$condition['value']);

                            // NOTE
                            // ... this is a DIRECT query, so format for adding here is a little diff
                            // ... and only works (not overriding existing ['direct']) because the calling func of this func has to especially copy separately
                            return array('additionalWhereArr'=>
                                            array('direct' => array(
                                                array('(SELECT COUNT(ID) FROM '.$ZBSCRM_t['taglinks'].' WHERE zbstl_objtype = %d AND zbstl_objid = contact.ID AND zbstl_tagid = %d) = 0',array(ZBS_TYPE_CONTACT,$condition['value']))
                                                )
                                            )
                                        ); 
                            break;

				default:
					break;

			}
		}

                // if we get here we've failed to create any arguments for this condiition
                // ... to avoid scenarios such as mail campaigns going out to 'less filtered than intended' audiences
                // ... we throw an error
                $this->error_condition_exception( 
                    'segment_condition_produces_no_args', 
                    __( 'Segment Condition produces no filtering arguments', 'zero-bs-crm'), 
                    array( 'condition' => $condition )
                 );

                return false;

           }

            // ONLY USED FOR SEGMENT SQL BUILING CURRENTLY, deep.
            // -----------------------
            // This was required to work with OR (e.g. postcode 1 = x or postcode 2 = x)
            // -----------------------
            // This generates a query like 'zbsc_fname LIKE %s OR zbsc_lname LIKE %s', 
            // which we then need to include as direct subquery
            public function segmentBuildDirectOrClause($directQueries=array(),$andOr='OR'){
            /* this works, in segmentConditionArgs(), adapted below to fit generic func to keep it DRY
                $conditionQArr = $this->buildWheres(array(
                                                    'email'.$conditionKeySuffix=>array('zbsc_email','=','%s',$condition['value']),
                                                    'emailAKA'.$conditionKeySuffix=>array('ID','IN',"(SELECT aka_id FROM ".$ZBSCRM_t['aka']." WHERE aka_type = ".ZBS_TYPE_CONTACT." AND aka_alias = %s)",$condition['value'])
                                                    ),'',array(),'OR',false);
                if (is_array($conditionQArr) && isset($conditionQArr['where']) && !empty($conditionQArr['where'])){                                    
                    return array('additionalWhereArr'=>array('direct'=>array(array('('.$conditionQArr['where'].')',$conditionQArr['params']))));
                }
                return array();

            */
                $directArr = $this->buildWheres($directQueries,'',array(),$andOr,false);
                if (is_array($directArr) && isset($directArr['where']) && !empty($directArr['where'])){                                    
                    return array('additionalWhereArr'=>array('direct'=>array(array('('.$directArr['where'].')',$directArr['params']))));
                }
                return array();
            }


           /**
             *  Compile a segment ()
             */
            public function compileSegment($segmentID=-1){

                if ( !empty( $segmentID ) ) {

                    // 'GET' the segment count without paging limits
                    // ... this func then automatically updates the compile record, so nothing to do :) 
                    return $this->getSegementAudience($segmentID,-1,-1,'ID','DESC',true);

                }

                return false;
            
           }

           /**
             *  Throw an exception
             */
            protected function error_condition_exception($code, $message, $data){

                throw new Segment_Condition_Exception( $code, $message, $data );

           }



           /**
             *  Checks that a segment audience can be compiled or if it has any outstanding errors
             *  ... returning as a string if so.
             *  ... for now this is done via a setting, later we should build an error stack via DAL #refined-error-stack
             */
            public function segment_error( $segment_id ){
                
                global $zbs;

                // sanitise and check
                $segment_id = (int)$segment_id;
                if ( $segment_id <= 0 ) return '';

                // Retrieve any setting value
                return $zbs->settings->get( 'segment-error-' . $segment_id );

           }

           /**
             *  Updates any stored segment audience error
             *  ... for now this is done via a setting, later we should build an error stack via DAL #refined-error-stack
             */
            public function add_segment_error( $segment_id, $error_string ){
                
                global $zbs;

                // sanitise and check
                $segment_id = (int)$segment_id;
                if ( $segment_id <= 0 ) return false;

                // Set segment area error notice
                $zbs->settings->update( 'segment-error-' . $segment_id, $error_string );                

           }


           /**
             *  Removes any stored segment audience error
             *  ... for now this is done via a setting, later we should build an error stack via DAL #refined-error-stack
             */
            public function remove_segment_error( $segment_id ){
    
                global $zbs;

                // sanitise and check
                $segment_id = (int)$segment_id;
                if ( $segment_id <= 0 ) return false;

                // Remove any segment area error notice
                $zbs->settings->delete( 'segment-error-' . $segment_id );                

           }


           /**
             *  Flags up a segment error where a used condition was missing in building a segment
             *
             * @param int - Segment ID
             * @param Exception - the related exception
             */
           public function segment_error_condition_missing( $segmentID, $exception ){

                // Not all conditions were able to produce arguments
                // Here we are best to return an empty audience and alert the admin
                // e.g. building a segment for a mail campaign without all conditions 
                // and sending to a larger-than-expected audience is more dangerous than sending to zero.
                $error_string =  $exception->get_error_code();

                // Set an admin notification to warn admin (max once every 2 days)                        
                $transient_flag = get_transient( 'crm-segment-condition-missing-arg' );
                if ( !$transient_flag ){

                    // insert notification
                    zeroBSCRM_notifyme_insert_notification( get_current_user_id(), -999, -1, 'segments.orphaned.conditions', 'segmentconditionmissing' );
                    
                    // set transient
                    set_transient( 'crm-segment-condition-missing-arg', 1, 24 * 2 * HOUR_IN_SECONDS );

                }

                // Set segment area error notice
                $this->add_segment_error( $segmentID, $error_string );
           }


           /**
             *  Migrates a superseded condition in the db to an up to date condition key
             *
             * @param string $superseded_key - the key of the old condition which has been replaced
             * @param string $new_key - the key to replace it with
             */
           public function migrate_superseded_condition( $superseded_key, $new_key ){

                global $zbs, $ZBSCRM_t, $wpdb;

                // very short and brutal. Ignores any ownership
                $query = "UPDATE " . $ZBSCRM_t['segmentsconditions'] . " SET zbscondition_type = %s WHERE zbscondition_type = %s";
                $q = $wpdb->prepare( $query, array( $new_key, $superseded_key ) );
                return $wpdb->query( $q );

           }




        

    // =========== / SEGMENTS      ===================================================
    // ===============================================================================
} // / class
