/**
  * Saves the sort of subcategories to the categories in the DB.
  *
  * @author Alexander Girin
  * @param array $catSortOrderArray the array of categories id, whose order
  *               defines sort_order of the categories.
  * @return
  */
 function setCategorySortOrder($catSortOrderArray)
 {
     global $application;
     /**
      * <pre>
      * BEFORE
      *
      * |
      * +-\ root (L_root, R_root)
      * | |                         L_1 = L_root + 1
      * | |-- subnode1 (L_1, R_1)
      * | |-- subnode2 (L_2, R_2)
      * | |-- subnode3 (L_3, R_3)
      * |                           R_3 = R_root - 1
      * |
      *
      * AFTER (new order: subnode2, subnode3, subnode1
      *
      * |
      * +-\ root (L_root, R_root)
      * | |              * = L_root + 1
      * | |-- subnode2 ( * , * + (R_2 - L_2))
      * | |-- subnode3 ( * , * + (R_3 - L_3))
      * | |-- subnode1 ( * , * + (R_1 - L_1))
      * |                   (* + (R_1 - L_1)) = R_root - 1
      * |
      * </pre>
      */
     /**
      * To make changes just update the indexes 'left' and 'right' only
      * into the subtree, whose root is the parent category for all sorted ones.
      * It can be used for optimization. The problem: how to change them all
      * together at once? If update the categories separately, then the errors
      * occur in the tree structure.
      */
     /**
      * Here is one of the answers to the previous question
      */
     // getting the exclusive rights
     $application->enterCriticalSection('setCategorySortOrder');
     // firstly we check if all the subcategories belong to the same category
     $parent_ids = execQuery('SELECT_PARENT_CATEGORY_IDS_FOR_CATEGORIES', array('cat_ids' => $catSortOrderArray));
     // if there are several parent categories (or none of them) then do nothing, category tree was changed
     if (count($parent_ids) != 1) {
         $application->leaveCriticalSection();
         return;
     }
     $parent_cat_id = $parent_ids[0]['parent_id'];
     // secondly we check if these subcategories are ALL subcategories of the parent category
     $other_cats = execQuery('SELECT_OTHER_SUBCATEGORIES_FOR_A_CATEGORY', array('id' => $parent_cat_id, 'cat_ids' => $catSortOrderArray));
     // if there are other subcategories then do nothing, category tree was changed
     if (!empty($other_cats)) {
         $application->leaveCriticalSection();
         return;
     }
     // getting base info for parent category
     $ParentCatInfo = $this->fetchCategoryInfo($parent_cat_id);
     // locking tables
     $query = new DB_MYSQL_Lock_Tables();
     $query->addTableToLock('categories', DB_LOCK_MODE_WRITE);
     $query->addTableToLock('categories_descr', DB_LOCK_MODE_WRITE);
     $query->addTableToLock('products', DB_LOCK_MODE_WRITE);
     $query->addTableToLock('store_settings', DB_LOCK_MODE_WRITE);
     $query->addTableToLock('products_to_categories', DB_LOCK_MODE_WRITE);
     $query->addTableToLock('events_manager', DB_LOCK_MODE_WRITE);
     $application->db->PrepareSQL($query);
     $application->db->DB_Exec($query);
     // clearing "old" sort data
     execQuery('UPDATE_CLEAR_SORT_CATEGORY_FIELD', array());
     // setting up the new offset inside the parent category. Based on it the current offset for each subcategory will be calculated
     $offset = $ParentCatInfo['left'] + 1;
     foreach ($catSortOrderArray as $cat_id) {
         // getting base info for the subcategory
         $CatInfo = $this->fetchCategoryInfo($cat_id);
         // calculating the offset for the category
         execQuery('UPDATE_SET_SORT_CATEGORY_FIELD', array('left' => $CatInfo['left'], 'right' => $CatInfo['right'], 'sort_order' => $offset - $CatInfo['left']));
         // recalculating the offset
         $offset += $CatInfo['right'] - $CatInfo['left'] + 1;
     }
     // all is done -> resorting... but a paranoidal check before
     if ($offset == $ParentCatInfo['right']) {
         execQuery('UPDATE_SORT_CATEGORIES', array());
         modApiFunc('EventsManager', 'throwEvent', 'CategorySortOrderChanged', $parent_cat_id);
     } else {
         execQuery('UPDATE_CLEAR_SORT_CATEGORY_FIELD', array());
     }
     // unlocking tables
     $query = new DB_MYSQL_Unlock_Tables();
     $application->db->PrepareSQL($query);
     $application->db->DB_Exec($query);
     $application->leaveCriticalSection();
 }
 /**
  * Gets database data dump, including the database structure.
  *
  * @return array data array
  */
 function getDataDump($tables, $currentTable, $currentLimit, $recordsExported, $fp, $link = 'db_link')
 {
     global ${$link};
     global $application;
     $table_engine = CConf::get('mysql_table_engine');
     $data = "";
     if ($currentTable == 0) {
         fwrite($fp, "-- --------------------------------------------------------\n-- Avactis Shopping Cart Software\n-- Database Backup\n-- Generation Time: " . date("M d, Y at h:i A", time()) . "\n-- --------------------------------------------------------\n");
     }
     $start_time = time();
     while (isset($tables[$currentTable])) {
         $query = new DB_MYSQL_Lock_Tables();
         $query->addTableToLock($tables[$currentTable]['table_name'], DB_LOCK_MODE_WRITE);
         $this->DB_Exec($query);
         if (!(time() - $start_time >= 2)) {
             if ($currentLimit == 0) {
                 $query = " SHOW CREATE TABLE " . $tables[$currentTable]['table_name'];
                 $query_result = $this->DB_Query($query);
                 $result = $this->DB_Fetch_Array($query_result, QUERY_RESULT_NUM);
                 $create_table_query = _ml_substr($result[1], 0, _ml_strrpos($result[1], "\n") + 1) . ") ENGINE={$table_engine};";
                 $data = "\n\n-- \n-- Table structure for table `" . $tables[$currentTable]['table_name'] . "`\n-- \n\nDROP TABLE IF EXISTS `" . $tables[$currentTable]['table_name'] . "`;\n";
                 $data .= $create_table_query;
                 $data .= "\n\n-- \n-- Dumping data for table `" . $tables[$currentTable]['table_name'] . "`\n-- \n\n";
                 fwrite($fp, $data);
             }
             $query = " SHOW COLUMNS FROM " . $tables[$currentTable]['table_name'];
             $query = $this->DB_Query($query);
             $columns = array();
             while ($table_column = $this->DB_Fetch_Array($query, QUERY_RESULT_ASSOC)) {
                 $columns[] = $table_column["Field"];
             }
             $query = " SELECT * FROM " . $tables[$currentTable]['table_name'] . " LIMIT " . 100 * $currentLimit . ", 100";
             $query = $this->DB_Query($query);
             $insert = true;
             $data = "";
             while ($row_data = $this->DB_Fetch_Array($query, QUERY_RESULT_ASSOC)) {
                 if ($insert) {
                     $table_name = $tables[$currentTable]['table_name'];
                     $data .= "LOCK TABLES `{$table_name}` WRITE;\nINSERT INTO `{$table_name}` (`" . implode("`, `", $columns) . "`) VALUES \n";
                     $insert = false;
                 }
                 $row = array();
                 foreach ($row_data as $key => $value) {
                     if (!is_int($key)) {
                         $row[] = "'" . addcslashes(addslashes($value), "..") . "'";
                     }
                 }
                 $data .= "(" . implode(", ", $row) . "),\n";
                 $recordsExported++;
             }
             if ($data && $data[_byte_strlen($data) - 2] == ",") {
                 $data[_byte_strlen($data) - 2] = ";";
                 fwrite($fp, $data . "UNLOCK TABLES;\n");
             }
             $currentLimit++;
             if ($currentLimit * 100 >= $tables[$currentTable]['records_count']) {
                 $currentLimit = 0;
                 $currentTable++;
             }
         } else {
             $query = new DB_MYSQL_Unlock_Tables();
             $this->DB_Exec($query);
             break;
         }
     }
     $retval = array("currentTable" => $currentTable, "currentLimit" => $currentLimit, "recordsExported" => $recordsExported);
     return $retval;
 }