/**
* Fix rollback table
*
* @param $table_name
* @param $table_info
* @param $entries
*
* @return boolean
* @access public
*/
function fix_rollback_table($table_name, $table_info, $entries = array())
{
    $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
    $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
    $success = TRUE;
    switch ($table_name) {
        // TODO: If individual tables are required to be handled specifically then it should be done here
        default:
            // For all rollback tables, delete all the duplicates expect one with min(eff_from) i.e. oldest one
            $keys = $table_info['primary_key'];
            if (isset($table_info['unique_key'])) {
                $keys = array_unique(array_merge($keys, $table_info['unique_key']));
            }
            if (empty($keys)) {
                echo "\nPrimary key fields not found the rollback table " . $table_name;
                $success = FALSE;
                break;
            }
            $where_sql = '';
            foreach ($keys as $key) {
                $where_sql .= $key . ' = :' . $key . ' AND ';
            }
            $where_sql .= 'sq_eff_to = :sq_eff_to';
            // Get the oldest 'eff_from'
            $sub_sql = 'SELECT min(sq_eff_from) FROM sq_rb_' . $table_name . ' WHERE ' . $where_sql;
            $sql = 'DELETE FROM sq_rb_' . $table_name . ' WHERE ' . $where_sql . ' AND sq_eff_from > (' . $sub_sql . ')';
            foreach ($entries as $entry) {
                try {
                    $update_sql = MatrixDAL::preparePdoQuery($sql);
                    foreach ($entry as $row_name => $row_val) {
                        if ($row_name == 'occ') {
                            continue;
                        }
                        MatrixDAL::bindValueToPdo($update_sql, $row_name, $row_val);
                    }
                    $execute = MatrixDAL::executePdoAssoc($update_sql);
                } catch (Exception $e) {
                    $success = FALSE;
                    break;
                }
            }
            break;
    }
    //end switch
    $GLOBALS['SQ_SYSTEM']->doTransaction($success ? 'COMMIT' : 'ROLLBACK');
    $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
    if (!$success) {
        echo "\nUnexpected error occured while updating database: " . $e->getMessage();
    }
    return $success;
}
/**
* Finds an existing asset matching the exact type with the metadata or attribute value supplied
*
* @return void
* @access public
*/
function findAsset($root_asset_id, $asset_type_code, array $search)
{
    // Begin uberquery!
    $db = MatrixDAL::getDb();
    $search_type_attribute = isset($search['attribute']);
    $field_name = '';
    $field_value = '';
    if ($search_type_attribute) {
        $field_name = $search['attribute']['field'];
        $field_value = $search['attribute']['value'];
    } else {
        $field_name = $search['metadata']['field'];
        $field_value = $search['metadata']['value'];
    }
    $tree_id = '';
    // Grab a single tree ID so we can search our entire root asset
    $sql = 'SELECT t.treeid FROM sq_ast_lnk_tree t, sq_ast_lnk l WHERE l.linkid = t.linkid AND l.minorid = :root_asset_id LIMIT 1';
    try {
        $query = MatrixDAL::preparePdoQuery($sql);
        MatrixDAL::bindValueToPdo($query, 'root_asset_id', $root_asset_id);
        $tree_id = MatrixDAL::executePdoOne($query);
    } catch (Exception $e) {
        throw new Exception('Unable to search for an existing ' . $asset_type_code . ' asset: ' . $e->getMessage());
    }
    if ($tree_id == '') {
        return array();
    }
    // Query portion for restricting by attribute
    $attribute_sql_from = 'sq_ast_attr r, sq_ast_attr_val v ';
    // Query portion for restricting by metadata field value
    $metadata_sql_from = 'sq_ast_mdata_val m ';
    $sql = 'SELECT a.assetid, a.name ' . 'FROM sq_ast a, sq_ast_lnk l, sq_ast_lnk_tree t, ' . ($search_type_attribute ? $attribute_sql_from : $metadata_sql_from) . 'WHERE t.treeid LIKE :tree_id ' . 'AND l.linkid = t.linkid AND a.assetid = l.minorid ';
    if (!empty($asset_type_code)) {
        $sql .= 'AND a.type_code = :type_code ';
    }
    if ($search_type_attribute) {
        $sql .= ' AND v.assetid = a.assetid AND r.name = :field_name AND v.attrid = r.attrid AND v.custom_val = :field_val';
    } else {
        $sql .= ' AND m.assetid = a.assetid AND m.fieldid = :field_name AND m.value = :field_val';
    }
    try {
        $query = MatrixDAL::preparePdoQuery($sql);
        MatrixDAL::bindValueToPdo($query, 'tree_id', $tree_id . '%');
        MatrixDAL::bindValueToPdo($query, 'field_name', $field_name);
        MatrixDAL::bindValueToPdo($query, 'field_val', $field_value);
        if (!empty($asset_type_code)) {
            MatrixDAL::bindValueToPdo($query, 'type_code', $asset_type_code);
        }
        $matching_assets = MatrixDAL::executePdoAssoc($query, 0);
    } catch (Exception $e) {
        throw new Exception('Unable to search for an existing ' . $asset_type_code . ' asset: ' . $e->getMessage());
    }
    return $matching_assets;
}
if (strtolower($yes_no) != 'y') {
    echo "\nScript aborted. \n";
    exit;
}
// log in as root
$root_user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('root_user');
if (!$GLOBALS['SQ_SYSTEM']->setCurrentUser($root_user)) {
    echo "ERROR: Failed login in as root user\n";
    exit;
}
// make some noiz
$db = $GLOBALS['SQ_SYSTEM']->db;
$sql = 'SELECT assetid FROM sq_ast WHERE type_code = :type_code';
$query = MatrixDAL::preparePdoQuery($sql);
MatrixDAL::bindValueToPdo($query, 'type_code', $DELETING_ASSET_TYPE);
$assets_of_type = MatrixDAL::executePdoAssoc($query, 0);
foreach ($assets_of_type as $index => $val) {
    $assets_of_type[$index] = MatrixDAL::quote($val);
}
if (!empty($assets_of_type)) {
    $asset_ids_set = '(' . implode(', ', $assets_of_type) . ')';
    $sql = 'DELETE FROM sq_ast_attr_val WHERE assetid in ' . $asset_ids_set;
    $query = MatrixDAL::preparePdoQuery($sql);
    MatrixDAL::execPdoQuery($query);
    $sql = 'DELETE FROM sq_ast_lnk WHERE minorid in ' . $asset_ids_set;
    $query = MatrixDAL::preparePdoQuery($sql);
    MatrixDAL::execPdoQuery($query);
    $sql = 'DELETE FROM sq_ast WHERE type_code = :type_code';
    $query = MatrixDAL::preparePdoQuery($sql);
    MatrixDAL::bindValueToPdo($query, 'type_code', $DELETING_ASSET_TYPE);
    MatrixDAL::execPdoQuery($query);
 /**
  * Retrieve data from matrix in order to perform tasks
  * Data retrieved is relevant to the current run settings and gives the
  * worker functions the information required to process the request
  *
  * @return array
  * @access protected
  **/
 protected function getAssetInfo()
 {
     $file = '/tmp/regenfs-assetdata-' . time() . '.tmp';
     $retrievingTemplate = 'Retrieving Asset Data' . "\t\t\t" . '%s   %s';
     printf($retrievingTemplate, '....', "\r");
     // Start a new child process
     $child = $this->pcntl->fork();
     if ($child === true) {
         // We're the child, let's do some work
         $this->matrixSetup();
         // Ensure Root node is a valid asset or die
         if (!$GLOBALS['SQ_SYSTEM']->am->assetExists($this->rootNode)) {
             trigger_error(sprintf('Root node \'%s\' does not exist!', $this->rootNode), E_USER_WARNING);
             posix_kill($this->pcntl->parentID, 9);
             exit(1);
         }
         // Get all contexts
         $contextids = array_keys($GLOBALS['SQ_SYSTEM']->getAllContexts());
         $assets = array();
         // If we're processing designs, include them on the list
         if ($this->processDesigns) {
             $assets['design'] = array_keys($GLOBALS['SQ_SYSTEM']->am->getChildren($this->rootNode, array('design', 'design_css'), true));
         }
         // If we're processing metadata, include them on the list
         if ($this->processMetadata) {
             $assets['metadata'] = array();
             $nodeassets = array_keys($GLOBALS['SQ_SYSTEM']->am->getChildren($this->rootNode));
             $nodeassets = array_chunk($nodeassets, 100);
             foreach ($nodeassets as $k => $entries) {
                 $keys = array_keys($entries);
                 // Add binding character
                 array_walk($keys, create_function('&$v,$k', '$v=\':a\'.$v;'));
                 // Implode key array into sql statement for pdo processing
                 $sql = 'select distinct assetid from sq_ast_mdata where assetid in (' . implode(',', $keys) . ')';
                 unset($keys);
                 $query = MatrixDAL::preparePdoQuery($sql);
                 // Bind keys to entry
                 foreach ($entries as $kk => $entry) {
                     MatrixDAL::bindValueToPdo($query, 'a' . $kk, $entry);
                 }
                 $metadata = MatrixDAL::executePdoAssoc($query);
                 foreach ($metadata as $k => $row) {
                     $assets['metadata'][] = $row['assetid'];
                 }
             }
         }
         // If we're processing bodycopies, include them on the asset list
         if ($this->processBodycopies) {
             $content_type_assetids = array_keys($GLOBALS['SQ_SYSTEM']->am->getChildren($this->rootNode, 'content_type', false));
             $assets['bodycopy'] = array();
             foreach ($content_type_assetids as $assetid) {
                 $bodycopy_container_link = $GLOBALS['SQ_SYSTEM']->am->getLinks($assetid, SQ_LINK_TYPE_2, array('bodycopy_container'), FALSE, 'minor');
                 if (isset($bodycopy_container_link[0]['majorid'])) {
                     $assets['bodycopy'][] = $bodycopy_container_link[0]['majorid'];
                 }
             }
         }
         $output = array();
         $output['contextids'] = $contextids;
         $output['assets'] = $assets;
         // Output data to the data file
         file_put_contents($file, serialize($output));
         exit(0);
     } else {
         if (is_numeric($child)) {
             // We're the parent, let's rest while the child is doing the chores
             while ($this->pcntl->childRunning($child) === true) {
                 sleep(1);
             }
             // Child has done it's chores, check to see if the file it needs to create is there, then process it
             if (file_exists($file)) {
                 // File is there, get the file contents and then delete the file
                 $response = file_get_contents($file);
                 unlink($file);
                 $response = @unserialize($response);
                 printf($retrievingTemplate, '[DONE]', "\n");
                 if (is_array($response)) {
                     return $response;
                 } else {
                     return array();
                 }
             }
         }
     }
 }
$GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
$GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
$sql = 'SELECT assetid FROM sq_ast WHERE type_code = :type_code';
$query = MatrixDAL::preparePdoQuery($sql);
MatrixDAL::bindValueToPdo($query, 'type_code', $DELETING_ASSET_TYPE);
$assets_of_type = MatrixDAL::executePdoAssoc($query, 0);
if (!empty($assets_of_type)) {
    // bugfix #3820	, use MatrixDAL to quote the asset ids
    foreach ($assets_of_type as $index => $asset_id) {
        $assets_of_type[$index] = MatrixDAL::quote($asset_id);
    }
    $asset_ids_set = '(' . implode(', ', $assets_of_type) . ')';
    $sql = 'SELECT linkid FROM sq_ast_lnk WHERE minorid in ' . $asset_ids_set . ' OR majorid in ' . $asset_ids_set;
    $query = MatrixDAL::preparePdoQuery($sql);
    // bugfix #3820
    $res = MatrixDAL::executePdoAssoc($query);
    // bugfix #3849 Script remove_assets_of_type.php leaves unwanted table entries
    // while deleting the link make sure we call asset_manager function to do the same
    // we should not be just deleting the links and treeids from the table as it can
    // lead to unexpected behaviour. for more information see the Bug report
    if (!empty($res)) {
        $links_array = array();
        foreach ($res as $value) {
            $GLOBALS['SQ_SYSTEM']->am->deleteAssetLink($value['linkid']);
            array_push($links_array, MatrixDAL::quote($value['linkid']));
        }
        $links_set = '(' . implode(', ', $links_array) . ')';
        $sql = 'DELETE FROM sq_ast_lnk WHERE linkid in ' . $links_set;
        $query = MatrixDAL::preparePdoQuery($sql);
        MatrixDAL::execPdoQuery($query);
    }
			JOIN ' . SQ_TABLE_RUNNING_PREFIX . 'ast_lnk l
			ON l.minorid = a.assetid';
$where = 'l.majorid IN (:assetid, :subfolder_assetid)
			AND i.inhd_type_code = :inhd_type_code
			AND a.created BETWEEN ' . db_extras_todate(MatrixDAL::getDbType(), ':created_from', FALSE) . '
			AND ' . db_extras_todate(MatrixDAL::getDbType(), ':created_to', FALSE);
$where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
$where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
$sql = $sql . $where . ' ORDER BY a.created DESC';
$query = MatrixDAL::preparePdoQuery($sql);
MatrixDAL::bindValueToPdo($query, 'assetid', $asset->id);
MatrixDAL::bindValueToPdo($query, 'subfolder_assetid', $sub_folder->id);
MatrixDAL::bindValueToPdo($query, 'inhd_type_code', 'form_submission');
MatrixDAL::bindValueToPdo($query, 'created_from', $from_value);
MatrixDAL::bindValueToPdo($query, 'created_to', $to_value);
$assetids = MatrixDAL::executePdoAssoc($query, 0);
if (empty($assetids)) {
    echo "No form submission found for '{$asset->name}' (#{$assetid}) within the specified date range\n";
    exit;
}
echo 'Found ' . count($assetids) . " form submission(s) for '{$asset->name}' (#{$assetid})\n(Date range: {$from_value} to {$to_value})\n";
$unquoted_assetids = $assetids;
// quote the assetids to be used in the IN clause
foreach ($assetids as $key => $assetid) {
    $assetids[$key] = MatrixDAL::quote((string) $assetid);
}
// break up the assets into chunks of 1000 so that oracle does not complain
$assetid_in = array();
foreach (array_chunk($assetids, 999) as $chunk) {
    $assetid_in[] = ' assetid IN (' . implode(', ', $chunk) . ')';
}
/**
* Returns TRUE if all the comma seperate URLs are valid site URLs
*
* @param string $value
*
* @return boolean
*/
function _valid_site_url(&$value)
{
    $urls = explode(',', $value);
    if (empty($urls)) {
        return FALSE;
    }
    $root_urls = explode("\n", SQ_CONF_SYSTEM_ROOT_URLS);
    $sql = 'SELECT url, http, https FROM sq_ast_url WHERE ';
    $bind_vars = array();
    $protocol = array();
    $valid_urls = array();
    foreach ($urls as $index => $full_url) {
        $url_parts = explode('://', $full_url);
        if (count($url_parts) != 2) {
            continue;
        }
        if (in_array($url_parts[1], $root_urls) && ($url_parts[0] == 'http' || $url_parts[0] == 'https')) {
            $valid_urls[] = $full_url;
        } else {
            $protocol_field = $url_parts[0] == 'http' ? 'http' : 'https';
            $sql .= ' (url = :url' . $index . ' AND ' . $protocol_field . ' = \'1\') OR';
            $bind_vars['url' . $index] = $url_parts[1];
            $protocol[$url_parts[1]] = $protocol_field;
        }
    }
    $sql = substr($sql, 0, strlen($sql) - 2);
    $query = MatrixDAL::preparePdoQuery($sql);
    foreach ($bind_vars as $var_name => $var_value) {
        MatrixDAL::bindValueToPdo($query, $var_name, $var_value);
    }
    $result = MatrixDAL::executePdoAssoc($query);
    foreach ($result as $row) {
        if ($row['http']) {
            $valid_urls[] = 'http://' . $row['url'];
        }
        //end if
        if ($row['https']) {
            $valid_urls[] = 'https://' . $row['url'];
        }
        //end if
    }
    //end forach protocols
    $invalid_sites = array_diff($urls, $valid_urls);
    if ($invalid_sites) {
        echo "ERROR: Following urls does not belongs to any site:\n" . implode("\n", $invalid_sites) . "\n";
        return FALSE;
    }
    if (empty($valid_urls)) {
        return FALSE;
    }
    // Sort urls by length i.e. longer url on top
    uasort($valid_urls, function ($a, $b) {
        return strlen($a) < strlen($b);
    });
    $value = $valid_urls;
    return TRUE;
}
/**
* Gets the children of the root nodes in the correct order from highest in the tree first to the
* lowest. Taken from HIPO_Job_Update_Lookups->prepare().
*
* @return array
* @access public
*/
function getTreeSortedChildren($assetids)
{
    $db = MatrixDAL::getDb();
    $todo_normal = array();
    $todo_shadows = array();
    foreach ($assetids as $assetid) {
        // check if we are updating lookups for a shadow asset, or a bridge
        $id_parts = explode(':', $assetid);
        if (isset($id_parts[1])) {
            $todo_shadows = array_merge($todo_shadows, array_keys($GLOBALS['SQ_SYSTEM']->am->getChildren($assetid)));
        } else {
            $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
            if ($asset instanceof Bridge) {
                if (!method_exists($asset, 'getChildren')) {
                    trigger_localised_error('SYS0204', translate('Shadow asset handler "%s" can not get children'), E_USER_WARNING, $asset->name);
                } else {
                    $todo_shadows = array_merge($todo_shadows, array_keys($asset->getChildren($assetid)));
                }
            }
            $where = 'l.minorid = :assetid';
            $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 't');
            $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
            $sql = 'SELECT t.treeid
					FROM ' . SQ_TABLE_RUNNING_PREFIX . 'ast_lnk_tree t INNER JOIN ' . SQ_TABLE_RUNNING_PREFIX . 'ast_lnk l ON t.linkid = l.linkid
					' . $where;
            $sql = db_extras_modify_limit_clause($sql, MatrixDAL::getDbType(), 1);
            try {
                $query = MatrixDAL::preparePdoQuery($sql);
                MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
                $treeid = MatrixDAL::executePdoOne($query);
            } catch (Exception $e) {
                throw new Exception('Unable to get treeid for minorid: ' . $assetid . ' due to database error: ' . $e->getMessage());
            }
            $sql = 'SELECT l.minorid, MAX(LENGTH(t.treeid)) as length
					FROM ' . SQ_TABLE_RUNNING_PREFIX . 'ast_lnk_tree t
							 INNER JOIN ' . SQ_TABLE_RUNNING_PREFIX . 'ast_lnk l ON t.linkid = l.linkid
					';
            $where = 't.treeid LIKE :treeid
					  GROUP BY l.minorid ORDER BY length';
            $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 't');
            $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
            try {
                $query = MatrixDAL::preparePdoQuery($sql . $where);
                MatrixDAL::bindValueToPdo($query, 'treeid', $treeid . '%');
                $new_assets = MatrixDAL::executePdoAssoc($query);
            } catch (Exception $e) {
                throw new Exception('Unable to get minorids for treeid: ' . $treeid[0]['treeid'] . ' due to database error: ' . $e->getMessage());
            }
            $todo_normal = array_merge($todo_normal, $new_assets);
        }
        //end else
    }
    //end foreach
    // Make sure lower assets are done after higher ones
    usort($todo_normal, create_function('$a, $b', 'return $a[\'length\'] > $b[\'length\'];'));
    $todo_assetids = array();
    foreach ($todo_normal as $asset_info) {
        $todo_assetids[] = $asset_info['minorid'];
    }
    $todo_assetids = array_unique(array_merge($todo_assetids, $todo_shadows));
    return $todo_assetids;
}
/**
* Ensures the sort order is linear taking into account the existing sort_order
* Begins from the provided root node and cleans all branches stemming from the provided root node
* Note: This is based on Tom's Tool_Asset_Sorter - the difference: Tom's tool is not based on existing sort_order and does not recurse
*
* @param array	$todo	Parents to sort
* @param array	$done	Parents done sorting
*
* @return boolean
* @access public
*/
function sortAssets($todo, $done)
{
    if (!empty($todo)) {
        $parentid = array_shift($todo);
        // order by existing sort_order
        // only concerned with TYPE_1 and TYPE_2
        // retrieve minorids as well because we need them for the recursive behaviour implemented towards the end of this routine
        $sql = 'SELECT linkid, minorid
				FROM sq_ast_lnk
				WHERE majorid = :parentid
					AND link_type IN (' . MatrixDAL::quote(SQ_LINK_TYPE_1) . ', ' . MatrixDAL::quote(SQ_LINK_TYPE_2) . ')
				ORDER BY sort_order ASC';
        try {
            $query = MatrixDAL::preparePdoQuery($sql);
            MatrixDAL::bindValueToPdo($query, 'parentid', $parentid);
            $results = MatrixDAL::executePdoAssoc($query);
        } catch (Exception $e) {
            throw new Exception('Unable to get linkids for parent: ' . $parentid . ' due to database error: ' . $e->getMessage());
        }
        echo "\n" . '- Updating the sort order for kids of: #' . $parentid . '...';
        // separate results
        $childids = $linkids = array();
        foreach ($results as $row) {
            // linkids used to update the sort_order
            $linkids[] = $row['linkid'];
            // childids used to look for more parents
            $childids[] = $row['minorid'];
        }
        if (!empty($linkids)) {
            // there is a limit to CASE statement size in Oracle, that limits it to
            // 127 WHEN-THEN pairs (in theory), so limit to 127 at a time on Oracle
            $db_type = MatrixDAL::getDbType();
            if ($db_type == 'oci') {
                $chunk_size = 127;
            } else {
                $chunk_size = 500;
            }
            $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
            foreach (array_chunk($linkids, $chunk_size, TRUE) as $chunk) {
                $cases = '';
                foreach ($chunk as $i => $linkid) {
                    $cases .= 'WHEN (linkid = ' . $linkid . ') THEN ' . $i . ' ';
                }
                $sql = 'UPDATE sq_ast_lnk
						SET sort_order = CASE ' . $cases . ' ELSE sort_order END
						WHERE linkid IN (' . implode(', ', $chunk) . ')';
                try {
                    $result = MatrixDAL::executeSql($sql);
                } catch (Exception $e) {
                    throw new Exception('Unable to update sort_order for parent: ' . $parentid . ' due to database error: ' . $e->getMessage());
                    $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
                    $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
                }
            }
            $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
        }
        // ensure we do not update this parent again
        if (!in_array($parentid, $done)) {
            $done[] = $parentid;
        }
        echo ' [done]';
        // check each child of the parent to see if the parent is a grandparent (i.e. parent's children have children)
        // only examining 1 level deep at a time
        if (!empty($childids)) {
            echo "\n\t" . '- Searching immediate children of: #' . $parentid . ' for branches';
            foreach ($childids as $assetid) {
                // check we have not processed it yet
                if (!in_array($assetid, $done)) {
                    // these are the kids that we have already sorted
                    // check to see if they are parents as well
                    // shadow asset links are ignored
                    $sql = 'SELECT minorid
							FROM sq_ast_lnk
							WHERE majorid = :assetid';
                    try {
                        $query = MatrixDAL::preparePdoQuery($sql);
                        MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
                        $children = MatrixDAL::executePdoAssoc($query);
                    } catch (Exception $e) {
                        throw new Exception('Unable to check children of parent: ' . $parentid . ' due to database error: ' . $e->getMessage());
                    }
                    if (!empty($children) && count($children) > 1) {
                        // we have a potential new parent
                        // check that the returned children contain at least one TYPE 1 or 2 linked asset
                        // e.g. asset could just be tagged with a thesaurus term (shadow link), meaning it is not a valid parent
                        $valid = FALSE;
                        foreach ($children as $grandchild) {
                            $link = $GLOBALS['SQ_SYSTEM']->am->getLink($grandchild['minorid'], NULL, '', TRUE, NULL, 'minor');
                            if (!empty($link) && ($link['link_type'] == SQ_LINK_TYPE_1 || $link['link_type'] == SQ_LINK_TYPE_2)) {
                                $valid = TRUE;
                                break;
                            }
                        }
                        if ($valid) {
                            echo "\n\t\t#" . $assetid . ' is a parent with kids that will be sorted';
                            $todo[] = $assetid;
                        }
                    }
                }
            }
        }
        echo "\n" . '* ' . count($todo) . ' items left to process' . "\n";
        echo '* Using ' . round(memory_get_usage() / 1048576, 2) . ' MB' . "\n";
        sortAssets($todo, $done);
    } else {
        // there are no more items to process
        return TRUE;
    }
}
// asset is Live, Safe Editting, Approved To Go Live, Under Construction, Safe Edit Approved to Go Live but it has running workflow by mistake.
$where = MatrixDAL::getDbType() === 'oci' ? ' AND DBMS_LOB.GETLENGTH(w.wflow) > 0' : ' AND w.wflow IS NOT NULL';
$sql = 'SELECT a.assetid, a.status FROM sq_ast_wflow w, sq_ast a WHERE a.assetid = w.assetid AND a.status IN (2, 8, 16, 64, 256)' . $where;
try {
    $query = MatrixDAL::preparePdoQuery($sql);
    $assets_should_not_have_workflow = MatrixDAL::executePdoAssoc($query);
} catch (Exception $e) {
    echo "ERROR: " . $e->getMessage() . "\n";
    exit(1);
}
// asset is Pending Approval but it does't have running workflow by mistake.
$where = MatrixDAL::getDbType() === 'oci' ? ' AND (w.wflow IS NULL OR DBMS_LOB.GETLENGTH(w.wflow) = 0)' : ' AND w.wflow IS NULL';
$sql = 'SELECT a.assetid, a.status FROM sq_ast a LEFT JOIN sq_ast_wflow w ON a.assetid = w.assetid WHERE a.status IN (4)' . $where;
try {
    $query = MatrixDAL::preparePdoQuery($sql);
    $assets_should_have_workflow = MatrixDAL::executePdoAssoc($query);
} catch (Exception $e) {
    echo "ERROR: " . $e->getMessage() . "\n";
    exit(1);
}
// nothing to do
if (empty($assets_should_have_workflow) && empty($assets_should_not_have_workflow)) {
    exit;
}
$workflows_to_update = array();
$assets_to_update = array();
ob_start();
if (!empty($assets_should_not_have_workflow)) {
    echo "\nFollowing assets are found to be in running workflow state but not in workflow status\n";
    foreach ($assets_should_not_have_workflow as $data) {
        echo "#" . $data['assetid'] . " has status " . $data['status'] . "\n";
/**
* Searches the Matrix System for an existing direct child asset which matches the specified name (and optionally, asset type)
* The asset IDs of matching assets are returned.
*
* @param int	$parent_id			The asset ID under which to search for direct matching child assets
* @param string	$asset_name			The asset name to match
* @param string	$asset_type_code	The asset type code to match (optional)
*
* @return array
* @access public
*/
function searchExistingAsset($parent_id, $asset_name, $asset_type_code = '')
{
    $db = MatrixDAL::getDb();
    $sql = 'SELECT l.minorid, a.name ' . 'FROM sq_ast_lnk l, sq_ast a ' . 'WHERE l.majorid = :majorid ';
    if (!empty($asset_type_code)) {
        $sql .= 'AND a.type_code = :type_code ';
    }
    $sql .= 'AND a.assetid = l.minorid ' . 'AND a.name = :asset_name';
    try {
        $query = MatrixDAL::preparePdoQuery($sql);
        MatrixDAL::bindValueToPdo($query, 'majorid', $parent_id);
        MatrixDAL::bindValueToPdo($query, 'asset_name', $asset_name);
        if (!empty($asset_type_code)) {
            MatrixDAL::bindValueToPdo($query, 'type_code', $asset_type_code);
        }
        $matching_assets = MatrixDAL::executePdoAssoc($query, 0);
    } catch (Exception $e) {
        throw new Exception('Unable to search for an existing ' . $asset_name . ' asset: ' . $e->getMessage());
    }
    return $matching_assets;
}
/**
 * Fixes the char encoding in the given tables in the database
 *
 * @param int		$root_node		Assetid of rootnode, all childern of rootnode will be processed for char replacement
 * @param array		$tables			DB tables and colunms info
 * @param boolean	$rollback		If TRUE process rollback tables, else process regular tables
 *
 * @return void
 */
function fix_db($root_node, $tables, $rollback)
{
    global $reportOnly;
    $tables_info = get_tables_info();
    // All the Matrix attribute types with serialised value
    $serialsed_attrs = array('option_list', 'email_format', 'parameter_map', 'serialise', 'http_request', 'oauth');
    // Get the list of attrids of the type 'serialise'
    $sql = "SELECT attrid FROM sq_ast_attr WHERE type IN ('" . implode("','", $serialsed_attrs) . "')";
    $serialise_attrids = array_keys(MatrixDAL::executeSqlGrouped($sql));
    if ($root_node != 1) {
        // Get the targetted asset list
        $target_assetids = array_keys($GLOBALS['SQ_SYSTEM']->am->getChildren($root_node));
        // Since we include the root node, target assetids will always contain atleast one asset id
        array_unshift($target_assetids, $root_node);
        echo "\n\nNumber of assets to look into : " . count($target_assetids) . " \n";
        // Go through 50 assets at a time. Applicable to asset specific tables only
        $chunks = array_chunk($target_assetids, 50);
        $chunks_count = count($chunks);
    }
    $errors_count = 0;
    $warnings_count = 0;
    $records_fixed_count = 0;
    $invalid_asset_records = array();
    // Assets that will require filesystem content regeneration
    $affected_assetids = array();
    $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
    // Counter to count the number of records accessed/processed
    $count = 0;
    foreach ($tables as $table_data) {
        $table_records_count = 0;
        $table = isset($table_data['table']) ? $table_data['table'] : '';
        if (empty($table)) {
            continue;
        }
        $key_fields = isset($tables_info[$table]['primary_key']) ? $tables_info[$table]['primary_key'] : '';
        if (empty($key_fields)) {
            echo "\n" . 'Ignoring table "' . $table . '". Table info for this table not found' . " \n";
            continue;
        }
        $value_fields = isset($table_data['values']) ? $table_data['values'] : '';
        if (empty($value_fields)) {
            // Nothing to check
            continue;
        }
        if ($rollback) {
            // Make sure table has rollback trigggers enabled, otherwise it will have rollback table
            if (isset($tables_info[$table]['rollback']) && $tables_info[$table]['rollback']) {
                // Add rollback table primary key field to the table's keys
                $key_fields[] = 'sq_eff_from';
            } else {
                // This table does not has corresponding rollback table
                continue;
            }
        }
        // Prepend table prefix
        $table = !$rollback ? 'sq_' . $table : 'sq_rb_' . $table;
        $asste_specific_table = $table_data['asset_assoc'];
        $select_fields = array_merge($value_fields, $key_fields);
        if ($asste_specific_table && !in_array('assetid', $select_fields)) {
            $select_fields[] = 'assetid';
        }
        if ($root_node == 1) {
            if ($asste_specific_table) {
                // When running system wide, get the asset list from the respective db table
                $sql = "SELECT DISTINCT assetid FROM " . $table;
                $target_assetids = array_keys(MatrixDAL::executeSqlGrouped($sql));
                // Go through 50 assets at a time. Applicable to asset specific tables only
                $chunks = array_chunk($target_assetids, 50);
            } else {
                // Dummy assetids chuck just so that we can get into next loop
                $chunks = array(array());
            }
        }
        echo "\nChecking " . $table . " .";
        // For non-asset specific table, this loop will break at end of the very first iteration
        foreach ($chunks as $chunk_index => $assetids) {
            $sql = 'SELECT ' . implode(',', $select_fields) . ' FROM ' . $table;
            // For non-asset specific table, "where" condition not is required. We get the whole table in a single go
            if ($asste_specific_table) {
                $sql .= ' WHERE assetid IN (\'' . implode('\',\'', $assetids) . '\')';
            } else {
                if ($table == 'sq_internal_msg') {
                    // Special case for non-asset specific records for 'interal_msg' table
                    // Internal message has 'assetid' field but messages not associated with the asset will have empty assetid
                    $sql .= " WHERE assetid = '' OR assetid IS NULL";
                }
            }
            $results = MatrixDAL::executeSqlAssoc($sql);
            foreach ($results as $record) {
                $table_records_count++;
                $count++;
                if ($count % 10000 == 0) {
                    echo '.';
                }
                // Asset ID associated with this record
                $assetid = $asste_specific_table ? $record['assetid'] : 'n/a';
                // Key field data
                $key_values = array();
                foreach ($key_fields as $key_field) {
                    $temp_key_v = array_get_index($record, $key_field, NULL);
                    if (is_null($temp_key_v)) {
                        // Primary key field must be there
                        continue 2;
                    }
                    $key_values[$key_field] = $temp_key_v;
                }
                //end foreach
                // Original value field data.
                // This is the one we need to check/fix
                $org_values = array();
                foreach ($value_fields as $value_field) {
                    $org_values[$value_field] = array_get_index($record, $value_field, '');
                }
                //end foreach
                // If it's the same in the new and old encodings, that's good.
                foreach ($org_values as $value_field => $value) {
                    $checked = @iconv(SYS_OLD_ENCODING, SYS_NEW_ENCODING . '//IGNORE', $value);
                    if ($value === $checked) {
                        // This field does not requires conversion/checking
                        unset($org_values[$value_field]);
                    }
                }
                //end foreach
                if (empty($org_values)) {
                    // No field values to convert/check
                    continue;
                }
                // Being here means this record contains invalid chars
                $invalid_asset_records[] = array('asset' => $assetid, 'table' => $table, 'keys' => $key_values, 'values' => $org_values);
                $converted_values = array();
                foreach ($org_values as $value_field => $value) {
                    // If not valid, convert the values without igonoring or interprating any chars
                    if (!isValidValue($value)) {
                        // Serialised fields needs to be handled here
                        $serialised_value = FALSE;
                        if ($table == 'sq_ast_attr_val' && $value_field == 'custom_val' && in_array($record['attrid'], $serialise_attrids)) {
                            $serialised_value = TRUE;
                        }
                        if ($table == 'sq_trig' && $value_field == 'data') {
                            $serialised_value = TRUE;
                        }
                        if ($serialised_value) {
                            $us_value = @unserialize($value);
                            if ($us_value === FALSE && serialize(FALSE) !== $value) {
                                // This has invalid serialsed value, but fix it anyway
                                $converted_value = @iconv(SYS_OLD_ENCODING, SYS_NEW_ENCODING . '//IGNORE', $value);
                                // Put this error notice in the script log file
                                $warnings_count++;
                                $msg = 'Serialsed data field "' . $value_field . '" in the table "' . $table . '" (';
                                foreach ($key_values as $field_name => $value) {
                                    $msg .= $field_name . '=' . $value . '; ';
                                }
                                $msg = rtrim($msg, '; ') . ') does not contain unserialisable data. ' . ($reportOnly ? 'Data can still be converted.' : 'Data will be converted anyway.');
                                log_error_msg($msg);
                            } else {
                                if (is_array($us_value)) {
                                    array_walk_recursive($us_value, 'fix_char');
                                    $converted_value = serialize($us_value);
                                } else {
                                    if (is_scalar($us_value)) {
                                        $us_value = @iconv(SYS_OLD_ENCODING, SYS_NEW_ENCODING . '//IGNORE', $us_value);
                                        $converted_value = serialize($us_value);
                                    } else {
                                        $converted_value = $value;
                                    }
                                }
                            }
                        } else {
                            $converted_value = @iconv(SYS_OLD_ENCODING, SYS_NEW_ENCODING . '//IGNORE', $value);
                        }
                        // If the converted value is valid in current encoding then its good to go
                        // otherwise we'll just not use this value
                        if ($converted_value != $value && isValidValue($converted_value)) {
                            $value = $converted_value;
                            $converted_values[$value_field] = $value;
                        }
                    } else {
                        // if it's a valid encoded value, but was convertable before with iconv using old encoding
                        // it might be only because value is already properly encoded with new encoding.  so use md_detect to double check
                        $encoding = mb_detect_encoding($value);
                        if (strtolower($encoding) === strtolower(SYS_NEW_ENCODING)) {
                            unset($org_values[$value_field]);
                        }
                    }
                }
                //end foreach
                if (empty($org_values)) {
                    // All good
                    array_pop($invalid_asset_records);
                    continue;
                }
                // If the successfully converted fields count is same as the invalid fields count, we can proceed with the update
                $update_required = count($org_values) == count($converted_values);
                if ($update_required) {
                    if (!$reportOnly) {
                        $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
                        // Generate update sql
                        $bind_vars = array();
                        $set_sql = array();
                        foreach ($converted_values as $field_name => $value) {
                            $set_sql[] = $field_name . '=:' . $field_name . '_v';
                            $bind_vars[$field_name . '_v'] = $value;
                        }
                        $where_sql = array();
                        foreach ($key_values as $field_name => $value) {
                            $where_sql[] = $field_name . '=:' . $field_name . '_k';
                            $bind_vars[$field_name . '_k'] = $value;
                        }
                        try {
                            $sql = 'UPDATE ' . $table . '
									SET ' . implode(', ', $set_sql) . '
									WHERE ' . implode(' AND ', $where_sql);
                            $update_sql = MatrixDAL::preparePdoQuery($sql);
                            foreach ($bind_vars as $var_name => $var_value) {
                                MatrixDAL::bindValueToPdo($update_sql, $var_name, $var_value);
                            }
                            // Execute the update query
                            $execute = MatrixDAL::executePdoAssoc($update_sql);
                            if (count($execute) > 1) {
                                foreach ($bind_vars as $var_name => $var_value) {
                                    $sql = str_replace(':' . $var_name, "'" . $var_value . "'", $sql);
                                }
                                $errors_count++;
                                $msg = 'Executing query "' . $sql . '" will affect ' . count($execute) . ' records, instead of expected single record! Ignoring this sql.';
                                log_error_msg($msg);
                                $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
                            } else {
                                $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
                                $records_fixed_count++;
                                $affected_assetids[$table][] = $assetid;
                            }
                        } catch (Exception $e) {
                            $errors_count++;
                            $msg = "Unexpected error occured while updating database: " . $e->getMessage();
                            log_error_msg($msg);
                            $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
                        }
                    } else {
                        $records_fixed_count++;
                        // For reporting purpose only
                        $affected_assetids[$table][] = $assetid;
                    }
                } else {
                    // Trying to carryout charset conversion for this invalid value still resulted into invalid value
                    // Hence record was not updated for this value conversion
                    $errors_count++;
                    $msg = 'Entry in the table "' . $table . '": ' . "\n";
                    foreach ($key_values as $field_name => $field_value) {
                        $msg .= $field_name . '="' . $field_value . '"; ';
                    }
                    $msg .= "\n" . 'contains invalid char(s), which were not replaced because the charset conversion was not successful' . ($msg .= "\n" . 'Potentially invalid characters include:' . listProblematicCharacters($org_values));
                    log_error_msg($msg);
                }
            }
            //end foreach records
            if (!$asste_specific_table) {
                // We have processed all the entries for this non-asset specific table
                break;
            }
        }
        //end foreach assetids chunk
        echo " " . $table_records_count . " records";
    }
    //end foreach tables
    $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
    unset($target_assetids);
    unset($chunks);
    echo "\n";
    $invalid_count = sizeof(array_keys($invalid_asset_records));
    echo "Number of db records with invalid char(s): " . $invalid_count . "\n";
    if ($invalid_count > 0) {
        foreach ($invalid_asset_records as $k => $details) {
            echo "\n\tAsset #" . $details['asset'] . " in table " . $details['table'];
            echo "\n\t" . 'Entry: ';
            foreach ($details['keys'] as $field_name => $field_value) {
                echo $field_name . '="' . $field_value . '"; ';
            }
            echo "\n\tPossibly problematic characters: " . listProblematicCharacters($details['values']) . "\n";
        }
        echo "\n";
    }
    return array('warning_count' => $warnings_count, 'error_count' => $errors_count, 'records_fixed_count' => $records_fixed_count, 'affected_assetids' => $affected_assetids);
}