From d951b81cca17385e982637bfc51eb84e9861938b Mon Sep 17 00:00:00 2001 From: Chuck Scott Date: Tue, 12 Apr 2016 11:26:45 -0400 Subject: [PATCH] Added field type "file" to the data abstract and a "file" table to be used with a file gallery (not supported yet) --- activate.php | 5 + classes/data/dataImages.php | 2 +- defines.php | 1 + glm-member-db.php | 2 +- lib/GlmDataAbstract/DataAbstract.php | 135 +- lib/GlmDataAbstract/documentation.odt | Bin 37803 -> 38584 bytes models/admin/management/import.php | 1042 +------------- models/admin/management/import.php.OLD | 1273 +++++++++++++++++ .../admin/management/import/memberImages.php | 58 + models/admin/management/import/members.php | 989 +++++++++++++ models/admin/member/memberInfo.php | 9 +- ..._V1.1.1.sql => create_database_V1.1.2.sql} | 19 + setup/databaseScripts/dbVersions.php | 6 +- ...se_V1.1.1.sql => drop_database_V1.1.2.sql} | 1 + setup/databaseScripts/readme.txt | 41 + .../update_database_V1.0.30.php | 2 +- .../update_database_V1.0.30.sql | 2 +- .../update_database_V1.0.43.sql | 2 +- .../update_database_V1.1.0.sql | 2 +- .../update_database_V1.1.1.sql | 2 +- .../update_database_V1.1.2.php | 13 + .../update_database_V1.1.2.sql | 25 + views/admin/management/import.html | 2 +- .../{importImages.html => memberImages.html} | 0 .../{readDatabase.html => members.html} | 2 +- views/admin/member/memberInfo.html | 2 +- 26 files changed, 2575 insertions(+), 1062 deletions(-) create mode 100644 models/admin/management/import.php.OLD create mode 100644 models/admin/management/import/memberImages.php create mode 100644 models/admin/management/import/members.php rename setup/databaseScripts/{create_database_V1.1.1.sql => create_database_V1.1.2.sql} (95%) rename setup/databaseScripts/{drop_database_V1.1.1.sql => drop_database_V1.1.2.sql} (96%) create mode 100644 setup/databaseScripts/readme.txt create mode 100644 setup/databaseScripts/update_database_V1.1.2.php create mode 100644 setup/databaseScripts/update_database_V1.1.2.sql rename views/admin/management/import/{importImages.html => memberImages.html} (100%) rename views/admin/management/import/{readDatabase.html => members.html} (97%) diff --git a/activate.php b/activate.php index 4a184422..a9d8ed76 100644 --- a/activate.php +++ b/activate.php @@ -114,6 +114,11 @@ class glmMembersPluginActivate extends glmPluginSupport mkdir(GLM_MEMBERS_PLUGIN_IMAGES_PATH); } + // Check if media directory has a subdirectory for files + if (!file_exists(GLM_MEMBERS_PLUGIN_FILES_PATH)) { + mkdir(GLM_MEMBERS_PLUGIN_FILES_PATH); + } + return; } diff --git a/classes/data/dataImages.php b/classes/data/dataImages.php index 9d9fe9af..2ec2a8ba 100644 --- a/classes/data/dataImages.php +++ b/classes/data/dataImages.php @@ -286,7 +286,7 @@ class GlmDataImages extends GlmDataAbstract if (isset($_REQUEST[$orderField]) && trim($_REQUEST[$orderField]) == '') { if (GLM_MEMBERS_PLUGIN_ADMIN_DEBUG) { - glmMembersAdmin::addNotice('dataImages: galleryPositionOrder() unable to find '.$orderField.'data.', 'Process'); + glmMembersAdmin::addNotice('dataImages: galleryPositionOrder() unable to find '.$orderField.' data. Perhaps there\'s no images at this time.', 'Process'); } return false; diff --git a/defines.php b/defines.php index 69fcb713..beb48cab 100644 --- a/defines.php +++ b/defines.php @@ -50,6 +50,7 @@ define('GLM_MEMBERS_PLUGIN_CLASS_PATH', GLM_MEMBERS_PLUGIN_PATH.'/classes'); define('GLM_MEMBERS_PLUGIN_LIB_PATH', GLM_MEMBERS_PLUGIN_PATH.'/lib'); define('GLM_MEMBERS_PLUGIN_MEDIA_PATH', $WPUploadDir['basedir'].'/'.GLM_MEMBERS_PLUGIN_SLUG); define('GLM_MEMBERS_PLUGIN_IMAGES_PATH', GLM_MEMBERS_PLUGIN_MEDIA_PATH.'/images'); +define('GLM_MEMBERS_PLUGIN_FILES_PATH', GLM_MEMBERS_PLUGIN_MEDIA_PATH.'/files'); define('GLM_MEMBERS_PLUGIN_CONFIG_PATH', GLM_MEMBERS_PLUGIN_PATH.'/config'); $pluginsPath = str_replace(GLM_MEMBERS_PLUGIN_SLUG, '', GLM_MEMBERS_PLUGIN_PATH); diff --git a/glm-member-db.php b/glm-member-db.php index 3cdcd790..704dab0d 100644 --- a/glm-member-db.php +++ b/glm-member-db.php @@ -39,7 +39,7 @@ */ define('GLM_MEMBERS_PLUGIN_VERSION', '1.0.58'); -define('GLM_MEMBERS_PLUGIN_DB_VERSION', '1.1.1'); +define('GLM_MEMBERS_PLUGIN_DB_VERSION', '1.1.2'); // Check if plugin version is not current in WordPress option and if needed updated it if (GLM_MEMBERS_PLUGIN_VERSION != get_option('glmMembersDatabasePluginVersion')) { diff --git a/lib/GlmDataAbstract/DataAbstract.php b/lib/GlmDataAbstract/DataAbstract.php index 2f1460e7..e39de524 100755 --- a/lib/GlmDataAbstract/DataAbstract.php +++ b/lib/GlmDataAbstract/DataAbstract.php @@ -92,6 +92,7 @@ abstract class GlmDataAbstract 'time', 'datetime', // Full date and time 'phone', + 'file', 'image', 'latitude', 'longitude' @@ -1194,6 +1195,7 @@ abstract class GlmDataAbstract } function textInput($as, $f, $id, $idField, $op) { + // If this is setup for a new entry, then just return default value if ($op == 'n') { $in = ''; @@ -1247,7 +1249,7 @@ abstract class GlmDataAbstract } if (isset($f['maxLength']) && $f['maxLength'] && strlen($in) > $f['maxLength']) { $this->inputFieldStatus = false; - $this->inputErrorReason = 'Input is longer than maximum length of '.$f['maxLength'].' characters.'; + $this->inputErrorReason = 'Input provided is '.strlen($in).' characters. The maximum length is '.$f['maxLength'].' characters.'; return $in; } @@ -1341,7 +1343,7 @@ abstract class GlmDataAbstract if (isset($f['maxLength'])) { if (trim($in) != '' && $f['maxLength'] && strlen($in) > $f['maxLength']) { $this->inputFieldStatus = false; - $this->inputErrorReason = 'Input is longer than maximum length of '.$f['maxLength'].' characters.'; + $this->inputErrorReason = 'Input provided is '.strlen($in).' characters. The maximum length is '.$f['maxLength'].' characters.'; return $in; } } @@ -2295,7 +2297,12 @@ abstract class GlmDataAbstract function imageInput($as, $f, $id, $idfield, $op) { - $haveNewImage = false; + $new = false; + + // Check if we have a new image being submitted + if (isset($_FILES[$as.'_new']) && is_array($_FILES[$as.'_new']) && $_FILES[$as.'_new']['tmp_name'] != '') { + $new = true; + } // If this is setup for a new entry, there is no default image capability if ($op == 'n') { @@ -2325,23 +2332,27 @@ abstract class GlmDataAbstract } // If there a request to delete an existing image or a new image and there's a current image - if (isset($_REQUEST[$as."_delete"]) && ($_REQUEST[$as."_delete"] == 'on' || $new) && $current_img != false) { + if ((isset($_REQUEST[$as."_delete"]) && $_REQUEST[$as."_delete"] == 'on') || ($new && $current_img != false)) { // Scan all the image size directories and remove this image while (list($k, $v) = each($this->config['imageSizes'])) { if (is_file(GLM_MEMBERS_PLUGIN_IMAGES_PATH.'/'.$k.'/'.$current_img)) { unlink(GLM_MEMBERS_PLUGIN_IMAGES_PATH.'/'.$k.'/'.$current_img); + } } - if (is_file(GLM_MEMBERS_PLUGIN_IMAGES_PATH.'/'.$current_img)) - unlink(GLM_MEMBERS_PLUGIN_IMAGES_PATH.'/'.$current_img); + + // And if there's a copy in the original image directory + if (is_file(GLM_MEMBERS_PLUGIN_IMAGES_PATH.'/original/'.$current_img)) { + unlink(GLM_MEMBERS_PLUGIN_IMAGES_PATH.'/original/'.$current_img); + } $current_img = ''; } // Is there a new image being uploaded - if (isset($_FILES[$as.'_new']) && is_array($_FILES[$as.'_new']) && $_FILES[$as.'_new']['tmp_name'] != '') { + if ($new) { // Get new image using temporary file name $newImage = wp_get_image_editor($_FILES[$as.'_new']['tmp_name']); @@ -2357,7 +2368,7 @@ abstract class GlmDataAbstract // Get the desired file name and add a timestamp to it to ensure that it's unique $fInfo = pathinfo($_FILES[$as.'_new']['name']); - $newFilename = strtolower($fInfo['filename'].'_'.time().'.'.$fInfo['extension']); + $newFilename = $prefix.strtolower($fInfo['filename'].'_'.time().'.'.$fInfo['extension']); // Get image temp file name - Not currently using, but should be using to check for resizing sanity $size = $newImage->get_size(); @@ -2369,6 +2380,7 @@ abstract class GlmDataAbstract $sizes = $newImage->multi_resize($this->config['imageSizes']); // Finally, move the files to the various size directories and rename them back to the correct name + reset($this->config['imageSizes']); while (list($k, $v) = each($this->config['imageSizes'])) { // Check if size directory needs to be made @@ -2418,6 +2430,113 @@ abstract class GlmDataAbstract return "'".addslashes($in)."'"; } + /* + * File Field Processing + */ + function fileField($f) + { + return 'T.'.$f['field']; + } + function fileOptions($f) + { + return false; + } + function fileOutput($f, $d) + { + return $d; + } + function fileInput($as, $f, $id, $idfield, $op) + { + + $new = false; + + // Check if we have a new file being submitted + if (isset($_FILES[$as.'_new']) && is_array($_FILES[$as.'_new']) && $_FILES[$as.'_new']['tmp_name'] != '') { + $new = true; + } + + // If this is setup for a new entry, there is no default file capability + if ($op == 'n') { + $in = ''; + return $in; + } + + $current_file = false; + + // Check if there's an existing file + if ($id != false) { + + // If no id field is specified, use 'id' + if ($idfield == false) { + $idfield = 'id'; + } + + // Get the current file + $sql = "SELECT $as + FROM $this->table + WHERE $idfield = $id;"; + $d = $this->wpdb->get_row($sql, ARRAY_A); + + if (trim($d[$as]) != '') { + $current_file = $d[$as]; + } + } + + // If there a request to delete an existing file or a new file and there's a current one + if ((isset($_REQUEST[$as."_delete"]) && $_REQUEST[$as."_delete"] == 'on') || ($new && $current_file != false)) { + + // Remove the current file + if (is_file(GLM_MEMBERS_PLUGIN_FILES_PATH.'/'.$current_file)) + unlink(GLM_MEMBERS_PLUGIN_FILES_PATH.'/'.$current_file); + + $current_file = ''; + + } + + // Is there a new file being uploaded + if ($new) { + + // Check if there's a maxSize option specified (specified in KBytes + if (isset($f['maxSize']) && trim($f['maxSize']) != '') { + + $max = ($f['maxSize'] - 0); + + // Get the size of the uploaded file + $fileSize = filesize($_FILES[$as.'_new']['tmp_name']); + + // If it's bigger than the max file size ($fileSize is in Bytes) + if ($fileSize > ($max * 1000)) { + $this->inputFieldStatus = false; + $this->inputErrorReason = 'File uploaded is '.intval($fileSize/1000).' kB. The maximum file size that may be submitted is '.$f['maxSize'].' kB.'; + return $in; + } + } + + + // Check if there's a prefix that should be included in the file name + $prefix = ''; + if (isset($f['f_prefix']) && trim($f['f_prefix']) != '') { + $prefix = $f['f_prefix']; + } + + // Get the desired file name and add a timestamp to it to ensure that it's unique + $fInfo = pathinfo($_FILES[$as.'_new']['name']); + $newFilename = $prefix.strtolower($fInfo['filename'].'_'.time().'.'.$fInfo['extension']); + + // Try to store the file using that file name + copy($_FILES[$as.'_new']['tmp_name'], GLM_MEMBERS_PLUGIN_FILES_PATH.'/'.$newFilename); + + $current_file = $newFilename; + + } + + return $current_file; + } + function fileStore($in, $f) + { + return "'".addslashes($in)."'"; + } + /* * latitude Field Processing */ diff --git a/lib/GlmDataAbstract/documentation.odt b/lib/GlmDataAbstract/documentation.odt index ff7687f755476dd8687553d6f4cfd87385cbe00d..21f30b1fc85e24b2caf5f0a42552124ff5af1982 100644 GIT binary patch delta 32542 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 Y`@d*a!vtKAu-F8j1WJhKxPKY{Kavg_6aWAK delta 31845 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 $dbServer, 'problem' => false); - if (!$dbServer || $dbServer == '' || $dbServer != trim($_REQUEST['dbServer'])) { - $templateData['dbServer']['problem'] = 'Server name or IP address was not provided or contained invalid characters.'; - $failure = true; - } - - // Check database port # - $dbPort = preg_replace("/[^0-9]+/", "", trim($_REQUEST['dbPort'])); - $templateData['dbPort'] = array('value' => $dbPort, 'problem' => false); - if (!$dbPort || $dbPort == '' || $dbPort != trim($_REQUEST['dbPort'])) { - $templateData['dbPort']['problem'] = 'Server port was not provided or is not a valid nummber.'; - $failure = true; - } - - // Check database name - $dbName = preg_replace("/[^a-zA-Z0-9_]+/", "", trim($_REQUEST['dbName'])); - $templateData['dbName'] = array('value' => $dbName, 'problem' => false); - if (!$dbName || $dbName == '' || $dbName != trim($_REQUEST['dbName'])) { - $templateData['dbName']['problem'] = 'Database name was not provided or is not valid for Postgres.'; - $failure = true; - } - - // Check database user - $dbUser = preg_replace("/[^a-zA-Z0-9_]+/", "", trim($_REQUEST['dbUser'])); - $templateData['dbUser'] = array('value' => $dbUser, 'problem' => false); - if (!$dbUser || $dbUser == '' || $dbUser != trim($_REQUEST['dbUser'])) { - $templateData['dbUser']['problem'] = 'Database user was not provided or is not valid for Postgres.'; - $failure = true; - } - - // Check Image URL - $dbImageURL = filter_var($_REQUEST['dbImageURL'], FILTER_SANITIZE_URL); - $templateData['dbImageURL'] = array('value' => $dbImageURL, 'problem' => false); - if (!$dbImageURL || $dbImageURL == '' || $dbImageURL != trim($_REQUEST['dbImageURL'])) { - $templateData['dbImageURL']['problem'] = 'Image URL does not appear to be valid.'; - $failure = true; - } - - if ($failure) { - $templateData['genError'] = 'There was a problem with the database connection information you provided. See below for specific instructions.'; - } - - /* - * Determine if source database is sane - */ - - // Connect to database - if (!$failure) { - $connString = "host=$dbServer port=$dbPort dbname=$dbName user=$dbUser"; - $db = @pg_connect($connString); - if (!$db) { - - $err = error_get_last(); - $templateData['genError'] = 'There was a problem connecting to the database. The error message was...
'.$err['message']; - $failure = true; - - } - } - - // Determine if the members schema exists - if (!$failure) { - $sql = " - SELECT EXISTS - ( - SELECT 1 - FROM information_schema.schemata AS exists - WHERE schema_name = 'members' - ) AS isMembers - ;"; - $res = pg_query($db, $sql); - if (pg_fetch_result($res, 0, 'isMembers') == 'f') { - $templateData['genError'] = 'The "members" schema was not found! Is this the right database?'; - $failure = true; - } - } - - /* - * Load data from source database - */ - - // Attempt to get member base data - if (!$failure) { - $sql = " - SELECT * - FROM members.member - ORDER BY member_id - ;"; - $res = pg_query($db, $sql); - $rows = pg_num_rows($res); - if ($rows == 0) { - $templateData['genError'] = 'There does not appear to be any members listed in this database!'; - $failure = true; - } else { - $member = pg_fetch_all($res); - if (count($member) != $rows) { - $notice = pg_last_notice($res); - $templateData['genError'] = 'While reading base member data, we did not receive the expected number of members! '; - if ($notice) { - $templateData['genError'] .= 'Perhaps the following message will help.
'.$notice; - } - $failure = true; - } - } - } - - // Read in all amenities - if (!$failure) { - $sql = " - SELECT * - FROM members.amenity - ORDER BY amenity_name - ;"; - $res = pg_query($db, $sql); - $rows = pg_num_rows($res); - if ($rows == 0) { - $templateData['genError'] = 'There does not appear to be any cities listed in this database!'; - $failure = true; - } else { - $tmp = pg_fetch_all($res); - if (count($tmp) != $rows) { - $notice = pg_last_notice($res); - $templateData['genError'] = 'While reading amenity data, we did not receive the expected number of amenities! '; - if ($notice) { - $templateData['genError'] .= 'Perhaps the following message will help.
'.$notice; - } - $failure = true; - } else { - - // Since there was no problem, we'll post-process the cities into an array indexed by city_id - $amenity = array(); - - // Reprocess into array indexed by amenity_id - foreach ($tmp as $x) { - $amenity[$x['amenity_id']] = $x; - } - } - } - } - - // Read in member/amenity cross-reference table - $numbAmenityMembers = 0; - if (!$failure) { - $sql = " - SELECT * - FROM members.member_amenity - ;"; - $res = pg_query($db, $sql); - $rows = pg_num_rows($res); - if ($rows == 0) { - $templateData['genError'] = 'There does not appear to be any member/amenity cross-reference entries listed in this database!'; - $failure = true; - } else { - $tmp = pg_fetch_all($res); - if (count($tmp) != $rows) { - $notice = pg_last_notice($res); - $templateData['genError'] = 'While reading amenity data, we did not receive the expected number of amenities! '; - if ($notice) { - $templateData['genError'] .= 'Perhaps the following message will help.
'.$notice; - } - $failure = true; - } else { - - // Since there was no problem, we'll post-process the ameities into an array indexed by amenity_id - $membAmen = array(); - - // Reprocess into array grouped by member with the main index the member ID - foreach ($tmp as $x) { - - // If member entry hasn't been created yet, add it now - if (!isset($membAmen[$x['member_id']])) { - $membAmen[$x['member_id']] = array(); - } - - $membAmen[$x['member_id']][] = $x; - $numbAmenityMembers++; - } - } - } - } - - // Read in all credit card types - if (!$failure) { - $sql = " - SELECT * - FROM members.ccard_type - ;"; - $res = pg_query($db, $sql); - $rows = pg_num_rows($res); - if ($rows == 0) { - $templateData['genError'] = 'There does not appear to be any credit card types listed in this database!'; - $failure = true; - } else { - $tmp = pg_fetch_all($res); - if (count($tmp) != $rows) { - $notice = pg_last_notice($res); - $templateData['genError'] = 'While reading credit card type data, we did not receive the expected number of credit card types! '; - if ($notice) { - $templateData['genError'] .= 'Perhaps the following message will help.
'.$notice; - } - $failure = true; - } else { - - // Since there was no problem, we'll post-process the cities into an array indexed by city_id - $ccard = array(); - - // Reprocess into array indexed by ccard_type_id - foreach ($tmp as $x) { - $ccard[$x['ccard_type_id']] = $x; - } - } - } - } - - // Read in member/credit card cross-reference table - $numbCcardMembers = 0; - if (!$failure) { - $sql = " - SELECT * - FROM members.member_ccard_type - ;"; - $res = pg_query($db, $sql); - $rows = pg_num_rows($res); - if ($rows == 0) { - $templateData['genError'] = 'There does not appear to be any member/credit card cross-reference entries listed in this database!'; - $failure = true; - } else { - $tmp = pg_fetch_all($res); - if (count($tmp) != $rows) { - $notice = pg_last_notice($res); - $templateData['genError'] = 'While reading member/credit card cross-reference data, we did not receive the expected number of entries! '; - if ($notice) { - $templateData['genError'] .= 'Perhaps the following message will help.
'.$notice; - } - $failure = true; - } else { - - // Since there was no problem, we'll post-process the cities into an array indexed by city_id - $membCcard = array(); - - // Reprocess into array grouped by member with the main index the member ID - foreach ($tmp as $x) { - - // If member entry hasn't been created yet, add it now - if (!isset($membCcard[$x['member_id']])) { - $membCcard[$x['member_id']] = array(); - } - - $membCcard[$x['member_id']][] = $x; - $numbCcardMembers++; - } - } - } - } - - - // Read in all member categories - $haveCatImportIssues = false; - $catImportIssues = ''; - if (!$failure) { - $sql = " - SELECT * - FROM members.category - ORDER BY category_id - ;"; - $res = pg_query($db, $sql); - $rows = pg_num_rows($res); - if ($rows == 0) { - $templateData['genError'] = 'There does not appear to be any categories listed in this database!'; - $failure = true; - } else { - $tmp = pg_fetch_all($res); - if (count($tmp) != $rows) { - $notice = pg_last_notice($res); - $templateData['genError'] = 'While reading category data, we did not receive the expected number of categories! '; - if ($notice) { - $templateData['genError'] .= 'Perhaps the following message will help.
'.$notice; - } - $failure = true; - } else { - - // Since there was no problem, we'll post-process the categories into a higherarchical array - $category = array(); - - // Process top level categories first - foreach ($tmp as $x) { - - // If this is a top level category - parent = 0 or points to itself - if ($x['parent_id'] == 0 || $x['parent_id'] == $x['category_id']) { - - foreach ($category as $y) { - if ($y['name'] == $x['name']) { - $haveCatImportIssues = true; - $catImportIssues .= '
  • Category '.$x['name'].' ('.$x['category_id'].') has the same name as another category at the same level. Added as duplicate.
  • '; - } - } - reset($category); - - $category[$x['category_id']] = $x; - $category[$x['category_id']]['subcat'] = array(); - - if ($x['parent_id'] == $x['category_id']) { - $haveCatImportIssues = true; - $catImportIssues .= '
  • Category '.$x['name'].' ('.$x['category_id'].') was pointing to itself as a parent. Now a top level category.
  • '; - } - } - - } - - // Now look for sub-categories with bad parents and add them as a main category - reset($tmp); - foreach ($tmp as $x) { - - // If this is NOT a top level category - Parent not 0 and not pointing to itself - if ($x['parent_id'] > 0 && $x['parent_id'] != $x['category_id']) { - - // Check if we don't have the requested parent as a top-level category - if (!isset($category[$x['parent_id']])) { - // Since the parent doesn't exist, we're just going to make this one a parent. - $haveCatImportIssues = true; - $catImportIssues .= '
  • Category '.$x['name'].' ('.$x['category_id'].') had a bad parent ID ('.$x['parent_id'].'). Now a top level category.
  • '; - $category[$x['category_id']] = $x; - $category[$x['category_id']]['subcat'] = array(); - } - } - } - - // Now process all supposedly good sub-categories - reset($tmp); - foreach ($tmp as $x) { - - // If this is NOT a top level category - Parent not 0 and not pointing to itself - if ($x['parent_id'] > 0 && $x['parent_id'] != $x['category_id']) { - - // Check if we have the requested parent as a top-level category - if (isset($category[$x['parent_id']])) { - $category[$x['parent_id']]['subcat'][$x['category_id']] = $x; - } - } - } - } - } - } - - // Read in member/Category cross-reference table - if (!$failure) { - $sql = " - SELECT * - FROM members.member_category - ;"; - $res = pg_query($db, $sql); - $rows = pg_num_rows($res); - if ($rows == 0) { - $templateData['genError'] = 'There does not appear to be any member/category cross-reference entries listed in this database!'; - $failure = true; - } else { - $tmp = pg_fetch_all($res); - if (count($tmp) != $rows) { - $notice = pg_last_notice($res); - $templateData['genError'] = 'While reading category data, we did not receive the expected number of categories! '; - if ($notice) { - $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; - } - $failure = true; - } else { - - // Since there was no problem, we'll post-process the cities into an array indexed by city_id - $membCat = array(); - - // Reprocess into array grouped by member with the main index the member ID - foreach ($tmp as $x) { - - // If member entry hasn't been created yet, add it now - if (!isset($membCat[$x['member_id']])) { - $membCat[$x['member_id']] = array(); - } - - $membCat[$x['member_id']][] = $x; - } - } - } - } - - // Read in all cities - if (!$failure) { - $sql = " - SELECT * - FROM members.city - ORDER BY city_name - ;"; - $res = pg_query($db, $sql); - $rows = pg_num_rows($res); - if ($rows == 0) { - $templateData['genError'] = 'There does not appear to be any cities listed in this database!'; - $failure = true; - } else { - $tmp = pg_fetch_all($res); - if (count($tmp) != $rows) { - $notice = pg_last_notice($res); - $templateData['genError'] = 'While reading city data, we did not receive the expected number of cities! '; - if ($notice) { - $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; - } - $failure = true; - } else { - - // Since there was no problem, we'll post-process the cities into an array indexed by city_id - $city = array(); - - // Reprocess into array indexed by city_id - foreach ($tmp as $x) { - $city[$x['city_id']] = $x; - } - } - } - } - - // Read in all states - if (!$failure) { - $sql = " - SELECT * - FROM members.state - ORDER BY state_name - ;"; - $res = pg_query($db, $sql); - $rows = pg_num_rows($res); - if ($rows == 0) { - $templateData['genError'] = 'There does not appear to be any states listed in this database!'; - $failure = true; - } else { - $tmp = pg_fetch_all($res); - if (count($tmp) != $rows) { - $notice = pg_last_notice($res); - $templateData['genError'] = 'While reading state data, we did not receive the expected number of states! '; - if ($notice) { - $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; - } - $failure = true; - } else { - - // Since there was no problem, we'll post-process the cities into an array indexed by city_id - $state = array(); - - // Reprocess into array indexed by state_id - foreach ($tmp as $x) { - $state[$x['state_id']] = $x; - } - } - } - } - - // Read in all regions - if (!$failure) { - $sql = " - SELECT * - FROM members.region - ;"; - $res = pg_query($db, $sql); - $rows = pg_num_rows($res); - if ($rows == 0) { - $templateData['genError'] = 'There does not appear to be any regions listed in this database!'; - $failure = true; - } else { - $tmp = pg_fetch_all($res); - if (count($tmp) != $rows) { - $notice = pg_last_notice($res); - $templateData['genError'] = 'While reading region data, we did not receive the expected number of regions! '; - if ($notice) { - $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; - } - $failure = true; - } else { - - // Since there was no problem, we'll post-process the cities into an array indexed by city_id - $region = array(); - - // Reprocess into array indexed by state_id - foreach ($tmp as $x) { - $region[$x['region_id']] = $x; - } - } - } - } - - // Read in all member photos for member image gallery - $numbImagesFound = 0; - if (!$failure) { - $sql = " - SELECT * - FROM members.member_photos - ;"; - $res = pg_query($db, $sql); - $rows = pg_num_rows($res); - if ($rows == 0) { - $templateData['genError'] = 'There does not appear to be any member photos listed in this database!'; - $failure = true; - } else { - $tmp = pg_fetch_all($res); - if (count($tmp) != $rows) { - $notice = pg_last_notice($res); - $templateData['genError'] = 'While reading member photo data, we did not receive the expected number of member photos! '; - if ($notice) { - $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; - } - $failure = true; - } else { - - // Since there was no problem, we'll post-process the cities into an array indexed by city_id - $image = array(); - - // Reprocess into array grouped by member with the main index the member ID - foreach ($tmp as $x) { - - // If member entry hasn't been created yet, add it now - if (!isset($image[$x['member_id']])) { - $image[$x['member_id']] = array( - 'member_id' => $x['member_id'], - 'images' => array() - ); - } - - $image[$x['member_id']]['images'][] = $x; - $numbImagesFound++; - } - } - } - } - - /* - * If requested, reset the database - */ - - // Reset database is Option is selected - if (!$failure && isset($_REQUEST['dbReset']) && $_REQUEST['dbReset'] == 'on') { - - // Get current db version - $dbVersion = GLM_MEMBERS_PLUGIN_DB_VERSION; - - // Reset the database - if (!$this->deleteDataTables($dbVersion)) { - glmMembersAdmin::addNotice('Unable to delete the database tables while resetting the database.
    ', 'AdminError'); - break; - } - if (!$this->createDataTables($dbVersion)) { - glmMembersAdmin::addNotice('Unable to create the database tables while resetting the database.
    ', 'AdminError'); - break; - } - - glmMembersAdmin::addNotice('Database tables have been reset in preparation importing members.
    ', 'AdminNotice'); - - // Delete Images - foreach( new RecursiveIteratorIterator( - - new RecursiveDirectoryIterator( GLM_MEMBERS_PLUGIN_IMAGES_PATH, FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS ), - RecursiveIteratorIterator::CHILD_FIRST ) as $value ) { - if ($value->isFile()) { - unlink( $value ); - } - } - - } - - /* - * Start importing data - */ - - if (!$failure) { - - // Import Cities - while (list ($key, $val) = each ($city) ) { - - $res = $this->wpdb->insert( - GLM_MEMBERS_PLUGIN_DB_PREFIX.'cities', - array( - 'name' => $val['city_name'], - ), - array( - '%s', - ) - ); - $city[$key]['new_id'] = $this->wpdb->insert_id; - - } - - // Import Regions - while (list ($key, $val) = each ($region) ) { - - $res = $this->wpdb->insert( - GLM_MEMBERS_PLUGIN_DB_PREFIX.'regions', - array( - 'name' => $val['region_name'], - 'descr' => '', - 'short_descr' => '' - ), - array( - '%s', - '%s', - '%s' - ) - ); - $region[$key]['new_id'] = $this->wpdb->insert_id; - - } - - // Import Amenities - while (list ($key, $val) = each ($amenity) ) { - - $res = $this->wpdb->insert( - GLM_MEMBERS_PLUGIN_DB_PREFIX.'amenities', - array( - 'active' => true, - 'name' => $val['amenity_name'], - 'descr' => '', - 'short_descr' => '', - 'ref_type' => $this->config['ref_type_numb']['MemberInfo'] - ), - array( - '%d', - '%s', - '%s', - '%s', - '%d' - ) - ); - $amenity[$key]['new_id'] = $this->wpdb->insert_id; - - } - - // Import Categories - $catTrans = array(); - $numbCategories = 0; - while (list ($key, $val) = each ($category) ) { - - $numbCategories++; - - $res = $this->wpdb->insert( - GLM_MEMBERS_PLUGIN_DB_PREFIX.'categories', - array( - 'name' => $val['name'], - 'descr' => '', - 'short_descr' => '', - 'parent' => 0 - ), - array( - '%s', - '%s', - '%s', - '%d' - ) - ); - $newID = $this->wpdb->insert_id; - $category[$key]['new_id'] = $newID; - $category[$key]['new_parent'] = 0; - - $catTrans[$key] = array( - 'new' => $newID, - 'parent' => 0 - ); - - // Do any sub-cats for this category - while (list ($key2, $val2) = each ($val['subcat']) ) { - - $res = $this->wpdb->insert( - GLM_MEMBERS_PLUGIN_DB_PREFIX.'categories', - array( - 'name' => $val2['name'], - 'descr' => '', - 'short_descr' => '', - 'parent' => $category[$key]['new_id'] - ), - array( - '%s', - '%s', - '%s', - '%d' - ) - ); - $newID2 = $this->wpdb->insert_id; - $category[$key]['subcat'][$key2]['new_id'] = $newID; - $category[$key]['subcat'][$key2]['new_parent'] = $category[$key]['new_id']; - - $catTrans[$key2] = array( - 'new' => $newID2, - 'parent' => $newID - ); - - } - } - - - - // Index Credit Card types - Match to card types in configuration - while (list ($key, $val) = each ($ccard) ) { - reset($this->config['credit_card_numb']); - while (list ($key2, $val2) = each ($this->config['credit_card_numb']) ) { - if (substr($val['ccard_type_name'], 0, 3) == substr(($key2), 0, 3)) { - $ccard[$key]['bitpos'] = $val2; - } - } - } - - /* - * Create Defaults Not Provided By Old Member DB - */ - - // Member Types - $res = $this->wpdb->insert( - GLM_MEMBERS_PLUGIN_DB_PREFIX.'member_type', - array( - 'name' => 'Default', - 'descr' => '', - ), - array( - '%s', - '%s', - ) - ); - $defaultMemberType = $this->wpdb->insert_id; - - /* - * Now Start Adding Members - */ - - // Import Members - $numbMembersActive = 0; - $numbMembersInactive = 0; - $namesInserted = array(); - $membImportIssues = ''; - $haveMembImportIssues = false; - $dupeNames = 0; - while (list ($key, $val) = each ($member) ) { - - // Determine if member is active and set access accordingly - if ($val['active'] == 't') { - - // Member is active, so set to active-moderated - $access = $this->config['access_numb']['Moderated']; - $numbMembersActive++; - - } else { - - // Member is not active, so set to no display no access - $access = $this->config['access_numb']['NotDisplayed']; - $numbMembersInactive++; - - } - - // Check for duplicate name - $membName = $val['member_name']; - if (isset($namesInserted[$membName])) { - - // Bump dupe count and add to name to make this one unique - $dupeNames++; - $membName .= ' DUPE-'.$dupeNames; - - $membImportIssues .= '
  • Member Duplicate '.$membName.'
  • '; - $haveMembImportIssues = true; - - } - - // Add main member record - $res = $this->wpdb->insert( - GLM_MEMBERS_PLUGIN_DB_PREFIX.'members', - array( - 'access' => $access, - 'member_type' => $defaultMemberType, - 'created' => date('Y-m-d'), - 'name' => $membName, - 'member_slug' => sanitize_title($val['member_name']), - 'old_member_id' => $val['member_id'] - ), - array( - '%d', - '%d', - '%s', - '%s', - '%s', - '%d' - ) - ); - $membID = $this->wpdb->insert_id; - $member[$key]['new_id'] = $membID; - - // Add this member to the names inserted so we can check for duplicates - $namesInserted[$membName] = true; - - // Create truncated short_descr from descritions - Less tags, html encoded characters, newlines, tabs, etc. - $stripped = str_replace(PHP_EOL, '', preg_replace('/\t+/', '', preg_replace("/&#?[a-z0-9]{2,8};/i", "", strip_tags($val['description'])))); - $short_descr = implode(' ', array_slice(explode(' ', $stripped), 0, 30)); - - if (strlen($short_descr) < strlen($stripped)) { - $short_descr .= ' ...'; - } - - // Build Credit Card bitmap - $ccBits = 0; - if (isset($membCcard[$val['member_id']])) { - foreach ($membCcard[$val['member_id']] as $c) { - $b = $ccard[$c['ccard_type_id']]['bitpos']; - $ccBits += pow(2, $b); - } - } - - // Insert Member Information Record - $res = $this->wpdb->insert( - GLM_MEMBERS_PLUGIN_DB_PREFIX.'member_info', - array( - 'member' => $membID, - 'member_name' => $val['member_name'], - 'status' => $this->config['status_numb']['Active'], - 'reference_name' => 'Imported Member Information', - 'descr' => $val['description'], - 'short_descr' => $short_descr, - 'addr1' => $val['street'], - 'addr2' => '', - 'city' => $city[$val['city_id']]['new_id'], - 'state' => $state[$val['state_id']]['state_abb'], - 'country' => 'US', - 'zip' => $val['zip'], - 'lat' => $val['lat'], - 'lon' => $val['lon'], - 'region' => (isset($region[$val['region']]) ? $region[$val['region']]['new_id'] : 0), - 'phone' => $val['phone'], - 'toll_free' => $val['toll_free'], - 'url' => $val['url'], - 'email' => $val['process_email'], - 'logo' => '', - 'cc_type' => $ccBits, - 'notes' => '', - 'create_time' => $val['create_date'], - 'modify_time' => $val['last_update'] - ), - array( - '%d', - '%s', - '%d', - '%s', - '%s', - '%s', - '%s', - '%s', - '%d', - '%s', - '%s', - '%s', - '%f', - '%f', - '%d', - '%s', - '%s', - '%s', - '%s', - '%s', - '%d', - '%s', - '%s', - '%s' - ) - ); - $infoID = $this->wpdb->insert_id; - $member[$key]['new_info_id'] = $infoID; - - // Add Member Categories - if (isset($membCat[$val['member_id']])) { - foreach ($membCat[$val['member_id']] as $c) { - - $res = $this->wpdb->insert( - GLM_MEMBERS_PLUGIN_DB_PREFIX.'category_member_info', - array( - 'category' => $catTrans[$c['category_id']]['new'], - 'member_info' => $infoID - ), - array( - '%d', - '%d' - ) - ); - - } - } - - // Add Member Amenities - if (isset($membAmen[$val['member_id']])) { - foreach ($membAmen[$val['member_id']] as $a) { - - $res = $this->wpdb->insert( - GLM_MEMBERS_PLUGIN_DB_PREFIX.'amenity_ref', - array( - 'amenity' => $amenity[$a['amenity_id']]['new_id'], - 'ref_type' => $this->config['ref_type_numb']['MemberInfo'], - 'ref_dest' => $infoID - ), - array( - '%d', - '%d', - '%d' - ) - ); - - } - } - - // Add logo to image array - if ($val['logo'] != '') { - // If member entry hasn't been created yet, add it now - if (!isset($image[$val['member_id']])) { - $image[$val['member_id']] = array( - 'member_id' => $val['member_id'], - 'images' => false - ); - } - $image[$val['member_id']]['logo'] = $val['logo']; - } - - // Update image list with new member IDs - if (isset($image[$val['member_id']])) { - $image[$val['member_id']]['new_memberinfo_id'] = $infoID; - } - - } - - // Import - - } - - // If everything is OK, make data available to the template - if (!$failure) { - - update_option( 'glm-member-db-import-imageurl', $dbImageURL); - update_option( 'glm-member-db-import-image', $image ); - - $templateData['numbCities'] = count($city); - $templateData['numbStates'] = count($state); - $templateData['numbRegions'] = count($region); - $templateData['numbMembers'] = count($member); - $templateData['numbMembersActive'] = $numbMembersActive; - $templateData['numbMembersInactive'] = $numbMembersInactive; - $templateData['haveMembImportIssues'] = $haveMembImportIssues; - $templateData['membImportIssues'] = $membImportIssues; - $templateData['numbCategories'] = count($catTrans); - $templateData['haveCatImportIssues'] = $haveCatImportIssues; - $templateData['catImportIssues'] = $catImportIssues; - $templateData['numbCategoryMembers'] = count($membCat); - $templateData['numbAmenities'] = count($amenity); - $templateData['numbAmenityMembers'] = $numbAmenityMembers; - $templateData['numbImagesFound'] = $numbImagesFound; - $templateData['numbCcards'] = count($ccard); - $templateData['numbCcardMembers'] = $numbCcardMembers; - - // For testing only - $templateData['member'] = $member; - $templateData['defaultMemberType'] = $defaultMemberType; - $templateData['amenity'] = $amenity; - $templateData['membAmen'] = $membAmen; - $templateData['category'] = $category; - $templateData['membCat'] = $membCat; - $templateData['catTrans'] = $catTrans; - $templateData['city'] = $city; - $templateData['state'] = $state; - $templateData['region'] = $region; - $templateData['ccard'] = $ccard; - $templateData['membCcard'] = $membCcard; - $templateData['image'] = $image; - } - - - if ($failure) { - return array( - 'status' => true, - 'menuItemRedirect' => 'management', - 'modelRedirect' => 'import', - 'view' => 'admin/management/import.html', - 'data' => $templateData - ); - - } - - $requestedView = 'import/readDatabase.html'; + require GLM_MEMBERS_PLUGIN_PATH.'/models/admin/management/import/members.php'; break; - case 'importImages': - - require_once(GLM_MEMBERS_PLUGIN_PATH.'/models/admin/ajax/imageUpload.php'); - $ImageUpload = new GlmMembersAdmin_ajax_imageUpload($this->wpdb, $this->config); - - $refType = $this->config['ref_type_numb']['MemberInfo']; - $refTable = $this->config['ref_type_table'][$refType]; - - // Get image array stored in a WordPress option - $imageBaseURL = get_option( 'glm-member-db-import-imageurl', false ); - $image = get_option( 'glm-member-db-import-image', false ); - - delete_option( 'glm-member-db-import-imageurl', false ); - delete_option( 'glm-member-db-import-image', false ); - - // If we have image URLs - if ($image != false) { - - // For each member - foreach ($image as $m) { - - // Import member logo - if (isset($m['logo'])) { - $imageURL = $imageBaseURL.$m['logo']; - $res = $ImageUpload->storeImage ($imageURL); - - // If we got a good new filename back, then it should be a good image store - if ($res['newFileName']) { - - // Update the member record with the logo image name - $res = $this->wpdb->update( - GLM_MEMBERS_PLUGIN_DB_PREFIX.'member_info', - array( - 'logo' => $res['newFileName'] - ), - array( 'id' => $m['new_memberinfo_id'] ), - array( '%s' ), - array( '%d' ) - ); - } - } - - // For each image in this member's gallery - if ($m['images']) { - foreach ($m['images'] as $i) { - $imageURL = $imageBaseURL.$i['image']; - $res = $ImageUpload->storeImage ($imageURL, $refType, $refTable, $m['new_memberinfo_id'], $i['caption']); - } - } - - } - - } + case 'images': - $requestedView = 'import/importImages.html'; + require GLM_MEMBERS_PLUGIN_PATH.'/models/admin/management/import/memberImages.php'; default: diff --git a/models/admin/management/import.php.OLD b/models/admin/management/import.php.OLD new file mode 100644 index 00000000..03ebac10 --- /dev/null +++ b/models/admin/management/import.php.OLD @@ -0,0 +1,1273 @@ + + * @license http://www.gaslightmedia.com Gaslightmedia + * @version 0.1 + */ + +/* + * This class performs the work for the default action of the "Members" menu + * option, which is to display the members dashboard. + * + */ +class GlmMembersAdmin_management_import +{ + + /** + * WordPress Database Object + * + * @var $wpdb + * @access public + */ + public $wpdb; + /** + * Plugin Configuration Data + * + * @var $config + * @access public + */ + public $config; + + /** + * Constructor + * + * This contructor sets up this model. At this time that only includes + * storing away the WordPress data object. + * + * @return object Class object + * + */ + public function __construct ($wpdb, $config) + { + + // Save WordPress Database object + $this->wpdb = $wpdb; + + // Save plugin configuration object + $this->config = $config; + + } + + /** + * Perform Model Action + * + * This method does the work for this model and returns any resulting data + * + * @return array Status and data array + * + * 'status' + * + * True if successfull and false if there was a fatal failure. + * + * 'menuItemRedirect' + * + * If not false, provides a menu item the controller should + * execute after this one. Normally if this is used, there would also be a + * modelRedirect value supplied as well. + * + * 'modelRedirect' + * + * If not false, provides an action the controller should execute after + * this one. + * + * 'view' + * + * A suggested view name that the contoller should use instead of the + * default view for this model or false to indicate that the default view + * should be used. + * + * 'data' + * + * Data that the model is returning for use in merging with the view to + * produce output. + * + */ + public function modelAction ($actionData = false) + { + + $templateData = array(); + + $requestedView = false; + $failure = false; + + $option = ''; + + // If there has been a redirect + if ($actionData) { + + $templateData = $actionData; + $option = 'importSelect'; + + // Otherwise check the request for desired option + } elseif (isset($_REQUEST['option']) && $_REQUEST['option'] != '') { + + $option = $_REQUEST['option']; + + // Fall back to importSelect + } else { + + $option = 'importSelect'; + + } + + switch($option) { + + case 'importSelect': + + $requestedView = 'import.html'; + + break; + + case 'readDatabase': + + /* + * Check all input + */ + + // Check hostname + $dbServer = preg_replace("/[^a-zA-Z0-9\._-]+/", "", trim($_REQUEST['dbServer'])); + $templateData['dbServer'] = array('value' => $dbServer, 'problem' => false); + if (!$dbServer || $dbServer == '' || $dbServer != trim($_REQUEST['dbServer'])) { + $templateData['dbServer']['problem'] = 'Server name or IP address was not provided or contained invalid characters.'; + $failure = true; + } + + // Check database port # + $dbPort = preg_replace("/[^0-9]+/", "", trim($_REQUEST['dbPort'])); + $templateData['dbPort'] = array('value' => $dbPort, 'problem' => false); + if (!$dbPort || $dbPort == '' || $dbPort != trim($_REQUEST['dbPort'])) { + $templateData['dbPort']['problem'] = 'Server port was not provided or is not a valid nummber.'; + $failure = true; + } + + // Check database name + $dbName = preg_replace("/[^a-zA-Z0-9_]+/", "", trim($_REQUEST['dbName'])); + $templateData['dbName'] = array('value' => $dbName, 'problem' => false); + if (!$dbName || $dbName == '' || $dbName != trim($_REQUEST['dbName'])) { + $templateData['dbName']['problem'] = 'Database name was not provided or is not valid for Postgres.'; + $failure = true; + } + + // Check database user + $dbUser = preg_replace("/[^a-zA-Z0-9_]+/", "", trim($_REQUEST['dbUser'])); + $templateData['dbUser'] = array('value' => $dbUser, 'problem' => false); + if (!$dbUser || $dbUser == '' || $dbUser != trim($_REQUEST['dbUser'])) { + $templateData['dbUser']['problem'] = 'Database user was not provided or is not valid for Postgres.'; + $failure = true; + } + + // Check Image URL + $dbImageURL = filter_var($_REQUEST['dbImageURL'], FILTER_SANITIZE_URL); + $templateData['dbImageURL'] = array('value' => $dbImageURL, 'problem' => false); + if (!$dbImageURL || $dbImageURL == '' || $dbImageURL != trim($_REQUEST['dbImageURL'])) { + $templateData['dbImageURL']['problem'] = 'Image URL does not appear to be valid.'; + $failure = true; + } + + if ($failure) { + $templateData['genError'] = 'There was a problem with the database connection information you provided. See below for specific instructions.'; + } + + /* + * Determine if source database is sane + */ + + // Connect to database + if (!$failure) { + $connString = "host=$dbServer port=$dbPort dbname=$dbName user=$dbUser"; + $db = @pg_connect($connString); + if (!$db) { + + $err = error_get_last(); + $templateData['genError'] = 'There was a problem connecting to the database. The error message was...
    '.$err['message']; + $failure = true; + + } + } + + // Determine if the members schema exists + if (!$failure) { + $sql = " + SELECT EXISTS + ( + SELECT 1 + FROM information_schema.schemata AS exists + WHERE schema_name = 'members' + ) AS isMembers + ;"; + $res = pg_query($db, $sql); + if (pg_fetch_result($res, 0, 'isMembers') == 'f') { + $templateData['genError'] = 'The "members" schema was not found! Is this the right database?'; + $failure = true; + } + } + + /* + * Load data from source database + */ + + // Attempt to get member base data + if (!$failure) { + $sql = " + SELECT * + FROM members.member + ORDER BY member_id + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any members listed in this database!'; + $failure = true; + } else { + $member = pg_fetch_all($res); + if (count($member) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading base member data, we did not receive the expected number of members! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } + } + } + + // Read in all amenities + if (!$failure) { + $sql = " + SELECT * + FROM members.amenity + ORDER BY amenity_name + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any cities listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading amenity data, we did not receive the expected number of amenities! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $amenity = array(); + + // Reprocess into array indexed by amenity_id + foreach ($tmp as $x) { + $amenity[$x['amenity_id']] = $x; + } + } + } + } + + // Read in member/amenity cross-reference table + $numbAmenityMembers = 0; + if (!$failure) { + $sql = " + SELECT * + FROM members.member_amenity + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any member/amenity cross-reference entries listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading amenity data, we did not receive the expected number of amenities! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the ameities into an array indexed by amenity_id + $membAmen = array(); + + // Reprocess into array grouped by member with the main index the member ID + foreach ($tmp as $x) { + + // If member entry hasn't been created yet, add it now + if (!isset($membAmen[$x['member_id']])) { + $membAmen[$x['member_id']] = array(); + } + + $membAmen[$x['member_id']][] = $x; + $numbAmenityMembers++; + } + } + } + } + + // Read in all credit card types + if (!$failure) { + $sql = " + SELECT * + FROM members.ccard_type + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any credit card types listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading credit card type data, we did not receive the expected number of credit card types! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $ccard = array(); + + // Reprocess into array indexed by ccard_type_id + foreach ($tmp as $x) { + $ccard[$x['ccard_type_id']] = $x; + } + } + } + } + + // Read in member/credit card cross-reference table + $numbCcardMembers = 0; + if (!$failure) { + $sql = " + SELECT * + FROM members.member_ccard_type + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any member/credit card cross-reference entries listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading member/credit card cross-reference data, we did not receive the expected number of entries! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $membCcard = array(); + + // Reprocess into array grouped by member with the main index the member ID + foreach ($tmp as $x) { + + // If member entry hasn't been created yet, add it now + if (!isset($membCcard[$x['member_id']])) { + $membCcard[$x['member_id']] = array(); + } + + $membCcard[$x['member_id']][] = $x; + $numbCcardMembers++; + } + } + } + } + + + // Read in all member categories + $haveCatImportIssues = false; + $catImportIssues = ''; + if (!$failure) { + $sql = " + SELECT * + FROM members.category + ORDER BY category_id + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any categories listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading category data, we did not receive the expected number of categories! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the categories into a higherarchical array + $category = array(); + + // Process top level categories first + foreach ($tmp as $x) { + + // If this is a top level category - parent = 0 or points to itself + if ($x['parent_id'] == 0 || $x['parent_id'] == $x['category_id']) { + + foreach ($category as $y) { + if ($y['name'] == $x['name']) { + $haveCatImportIssues = true; + $catImportIssues .= '
  • Category '.$x['name'].' ('.$x['category_id'].') has the same name as another category at the same level. Added as duplicate.
  • '; + } + } + reset($category); + + $category[$x['category_id']] = $x; + $category[$x['category_id']]['subcat'] = array(); + + if ($x['parent_id'] == $x['category_id']) { + $haveCatImportIssues = true; + $catImportIssues .= '
  • Category '.$x['name'].' ('.$x['category_id'].') was pointing to itself as a parent. Now a top level category.
  • '; + } + } + + } + + // Now look for sub-categories with bad parents and add them as a main category + reset($tmp); + foreach ($tmp as $x) { + + // If this is NOT a top level category - Parent not 0 and not pointing to itself + if ($x['parent_id'] > 0 && $x['parent_id'] != $x['category_id']) { + + // Check if we don't have the requested parent as a top-level category + if (!isset($category[$x['parent_id']])) { + // Since the parent doesn't exist, we're just going to make this one a parent. + $haveCatImportIssues = true; + $catImportIssues .= '
  • Category '.$x['name'].' ('.$x['category_id'].') had a bad parent ID ('.$x['parent_id'].'). Now a top level category.
  • '; + $category[$x['category_id']] = $x; + $category[$x['category_id']]['subcat'] = array(); + } + } + } + + // Now process all supposedly good sub-categories + reset($tmp); + foreach ($tmp as $x) { + + // If this is NOT a top level category - Parent not 0 and not pointing to itself + if ($x['parent_id'] > 0 && $x['parent_id'] != $x['category_id']) { + + // Check if we have the requested parent as a top-level category + if (isset($category[$x['parent_id']])) { + $category[$x['parent_id']]['subcat'][$x['category_id']] = $x; + } + } + } + } + } + } + + // Read in member/Category cross-reference table + if (!$failure) { + $sql = " + SELECT * + FROM members.member_category + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any member/category cross-reference entries listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading category data, we did not receive the expected number of categories! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $membCat = array(); + + // Reprocess into array grouped by member with the main index the member ID + foreach ($tmp as $x) { + + // If member entry hasn't been created yet, add it now + if (!isset($membCat[$x['member_id']])) { + $membCat[$x['member_id']] = array(); + } + + $membCat[$x['member_id']][] = $x; + } + } + } + } + + // Read in all cities + if (!$failure) { + $sql = " + SELECT * + FROM members.city + ORDER BY city_name + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any cities listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading city data, we did not receive the expected number of cities! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $city = array(); + + // Reprocess into array indexed by city_id + foreach ($tmp as $x) { + $city[$x['city_id']] = $x; + } + } + } + } + + // Read in all states + if (!$failure) { + $sql = " + SELECT * + FROM members.state + ORDER BY state_name + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any states listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading state data, we did not receive the expected number of states! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $state = array(); + + // Reprocess into array indexed by state_id + foreach ($tmp as $x) { + $state[$x['state_id']] = $x; + } + } + } + } + + // Read in all regions + if (!$failure) { + $sql = " + SELECT * + FROM members.region + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any regions listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading region data, we did not receive the expected number of regions! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $region = array(); + + // Reprocess into array indexed by state_id + foreach ($tmp as $x) { + $region[$x['region_id']] = $x; + } + } + } + } + + // Read in all member photos for member image gallery + $numbImagesFound = 0; + if (!$failure) { + $sql = " + SELECT * + FROM members.member_photos + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any member photos listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading member photo data, we did not receive the expected number of member photos! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $image = array(); + + // Reprocess into array grouped by member with the main index the member ID + foreach ($tmp as $x) { + + // If member entry hasn't been created yet, add it now + if (!isset($image[$x['member_id']])) { + $image[$x['member_id']] = array( + 'member_id' => $x['member_id'], + 'images' => array() + ); + } + + $image[$x['member_id']]['images'][] = $x; + $numbImagesFound++; + } + } + } + } + + /* + * If requested, reset the database + */ + + // Reset database is Option is selected + if (!$failure && isset($_REQUEST['dbReset']) && $_REQUEST['dbReset'] == 'on') { + + // Get current db version + $dbVersion = GLM_MEMBERS_PLUGIN_DB_VERSION; + + // Reset the database + if (!$this->deleteDataTables($dbVersion)) { + glmMembersAdmin::addNotice('Unable to delete the database tables while resetting the database.
    ', 'AdminError'); + break; + } + if (!$this->createDataTables($dbVersion)) { + glmMembersAdmin::addNotice('Unable to create the database tables while resetting the database.
    ', 'AdminError'); + break; + } + + glmMembersAdmin::addNotice('Database tables have been reset in preparation importing members.
    ', 'AdminNotice'); + + // Delete Images + foreach( new RecursiveIteratorIterator( + + new RecursiveDirectoryIterator( GLM_MEMBERS_PLUGIN_IMAGES_PATH, FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS ), + RecursiveIteratorIterator::CHILD_FIRST ) as $value ) { + if ($value->isFile()) { + unlink( $value ); + } + } + + } + + /* + * Start importing data + */ + + if (!$failure) { + + // Import Cities + while (list ($key, $val) = each ($city) ) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'cities', + array( + 'name' => $val['city_name'], + ), + array( + '%s', + ) + ); + $city[$key]['new_id'] = $this->wpdb->insert_id; + + } + + // Import Regions + while (list ($key, $val) = each ($region) ) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'regions', + array( + 'name' => $val['region_name'], + 'descr' => '', + 'short_descr' => '' + ), + array( + '%s', + '%s', + '%s' + ) + ); + $region[$key]['new_id'] = $this->wpdb->insert_id; + + } + + // Import Amenities + while (list ($key, $val) = each ($amenity) ) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'amenities', + array( + 'active' => true, + 'name' => $val['amenity_name'], + 'descr' => '', + 'short_descr' => '', + 'ref_type' => $this->config['ref_type_numb']['MemberInfo'] + ), + array( + '%d', + '%s', + '%s', + '%s', + '%d' + ) + ); + $amenity[$key]['new_id'] = $this->wpdb->insert_id; + + } + + // Import Categories + $catTrans = array(); + $numbCategories = 0; + while (list ($key, $val) = each ($category) ) { + + $numbCategories++; + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'categories', + array( + 'name' => $val['name'], + 'descr' => '', + 'short_descr' => '', + 'parent' => 0 + ), + array( + '%s', + '%s', + '%s', + '%d' + ) + ); + $newID = $this->wpdb->insert_id; + $category[$key]['new_id'] = $newID; + $category[$key]['new_parent'] = 0; + + $catTrans[$key] = array( + 'new' => $newID, + 'parent' => 0 + ); + + // Do any sub-cats for this category + while (list ($key2, $val2) = each ($val['subcat']) ) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'categories', + array( + 'name' => $val2['name'], + 'descr' => '', + 'short_descr' => '', + 'parent' => $category[$key]['new_id'] + ), + array( + '%s', + '%s', + '%s', + '%d' + ) + ); + $newID2 = $this->wpdb->insert_id; + $category[$key]['subcat'][$key2]['new_id'] = $newID; + $category[$key]['subcat'][$key2]['new_parent'] = $category[$key]['new_id']; + + $catTrans[$key2] = array( + 'new' => $newID2, + 'parent' => $newID + ); + + } + } + + + + // Index Credit Card types - Match to card types in configuration + while (list ($key, $val) = each ($ccard) ) { + reset($this->config['credit_card_numb']); + while (list ($key2, $val2) = each ($this->config['credit_card_numb']) ) { + if (substr($val['ccard_type_name'], 0, 3) == substr(($key2), 0, 3)) { + $ccard[$key]['bitpos'] = $val2; + } + } + } + + /* + * Create Defaults Not Provided By Old Member DB + */ + + // Member Types + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'member_type', + array( + 'name' => 'Default', + 'descr' => '', + ), + array( + '%s', + '%s', + ) + ); + $defaultMemberType = $this->wpdb->insert_id; + + /* + * Now Start Adding Members + */ + + // Import Members + $numbMembersActive = 0; + $numbMembersInactive = 0; + $namesInserted = array(); + $membImportIssues = ''; + $haveMembImportIssues = false; + $dupeNames = 0; + while (list ($key, $val) = each ($member) ) { + + // Determine if member is active and set access accordingly + if ($val['active'] == 't') { + + // Member is active, so set to active-moderated + $access = $this->config['access_numb']['Moderated']; + $numbMembersActive++; + + } else { + + // Member is not active, so set to no display no access + $access = $this->config['access_numb']['NotDisplayed']; + $numbMembersInactive++; + + } + + // Check for duplicate name + $membName = $val['member_name']; + if (isset($namesInserted[$membName])) { + + // Bump dupe count and add to name to make this one unique + $dupeNames++; + $membName .= ' DUPE-'.$dupeNames; + + $membImportIssues .= '
  • Member Duplicate '.$membName.'
  • '; + $haveMembImportIssues = true; + + } + + // Add main member record + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'members', + array( + 'access' => $access, + 'member_type' => $defaultMemberType, + 'created' => date('Y-m-d'), + 'name' => $membName, + 'member_slug' => sanitize_title($val['member_name']), + 'old_member_id' => $val['member_id'] + ), + array( + '%d', + '%d', + '%s', + '%s', + '%s', + '%d' + ) + ); + $membID = $this->wpdb->insert_id; + $member[$key]['new_id'] = $membID; + + // Add this member to the names inserted so we can check for duplicates + $namesInserted[$membName] = true; + + // Create truncated short_descr from descritions - Less tags, html encoded characters, newlines, tabs, etc. + $stripped = str_replace(PHP_EOL, '', preg_replace('/\t+/', '', preg_replace("/&#?[a-z0-9]{2,8};/i", "", strip_tags($val['description'])))); + $short_descr = implode(' ', array_slice(explode(' ', $stripped), 0, 30)); + + if (strlen($short_descr) < strlen($stripped)) { + $short_descr .= ' ...'; + } + + // Build Credit Card bitmap + $ccBits = 0; + if (isset($membCcard[$val['member_id']])) { + foreach ($membCcard[$val['member_id']] as $c) { + $b = $ccard[$c['ccard_type_id']]['bitpos']; + $ccBits += pow(2, $b); + } + } + + // Insert Member Information Record + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'member_info', + array( + 'member' => $membID, + 'member_name' => $val['member_name'], + 'status' => $this->config['status_numb']['Active'], + 'reference_name' => 'Imported Member Information', + 'descr' => $val['description'], + 'short_descr' => $short_descr, + 'addr1' => $val['street'], + 'addr2' => '', + 'city' => $city[$val['city_id']]['new_id'], + 'state' => $state[$val['state_id']]['state_abb'], + 'country' => 'US', + 'zip' => $val['zip'], + 'lat' => $val['lat'], + 'lon' => $val['lon'], + 'region' => (isset($region[$val['region']]) ? $region[$val['region']]['new_id'] : 0), + 'phone' => $val['phone'], + 'toll_free' => $val['toll_free'], + 'url' => $val['url'], + 'email' => $val['process_email'], + 'logo' => '', + 'cc_type' => $ccBits, + 'notes' => '', + 'create_time' => $val['create_date'], + 'modify_time' => $val['last_update'] + ), + array( + '%d', + '%s', + '%d', + '%s', + '%s', + '%s', + '%s', + '%s', + '%d', + '%s', + '%s', + '%s', + '%f', + '%f', + '%d', + '%s', + '%s', + '%s', + '%s', + '%s', + '%d', + '%s', + '%s', + '%s' + ) + ); + $infoID = $this->wpdb->insert_id; + $member[$key]['new_info_id'] = $infoID; + + // Add Member Categories + if (isset($membCat[$val['member_id']])) { + foreach ($membCat[$val['member_id']] as $c) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'category_member_info', + array( + 'category' => $catTrans[$c['category_id']]['new'], + 'member_info' => $infoID + ), + array( + '%d', + '%d' + ) + ); + + } + } + + // Add Member Amenities + if (isset($membAmen[$val['member_id']])) { + foreach ($membAmen[$val['member_id']] as $a) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'amenity_ref', + array( + 'amenity' => $amenity[$a['amenity_id']]['new_id'], + 'ref_type' => $this->config['ref_type_numb']['MemberInfo'], + 'ref_dest' => $infoID + ), + array( + '%d', + '%d', + '%d' + ) + ); + + } + } + + // Add logo to image array + if ($val['logo'] != '') { + // If member entry hasn't been created yet, add it now + if (!isset($image[$val['member_id']])) { + $image[$val['member_id']] = array( + 'member_id' => $val['member_id'], + 'images' => false + ); + } + $image[$val['member_id']]['logo'] = $val['logo']; + } + + // Update image list with new member IDs + if (isset($image[$val['member_id']])) { + $image[$val['member_id']]['new_memberinfo_id'] = $infoID; + } + + } + + // Import + + } + + // If everything is OK, make data available to the template + if (!$failure) { + + update_option( 'glm-member-db-import-imageurl', $dbImageURL); + update_option( 'glm-member-db-import-image', $image ); + + $templateData['numbCities'] = count($city); + $templateData['numbStates'] = count($state); + $templateData['numbRegions'] = count($region); + $templateData['numbMembers'] = count($member); + $templateData['numbMembersActive'] = $numbMembersActive; + $templateData['numbMembersInactive'] = $numbMembersInactive; + $templateData['haveMembImportIssues'] = $haveMembImportIssues; + $templateData['membImportIssues'] = $membImportIssues; + $templateData['numbCategories'] = count($catTrans); + $templateData['haveCatImportIssues'] = $haveCatImportIssues; + $templateData['catImportIssues'] = $catImportIssues; + $templateData['numbCategoryMembers'] = count($membCat); + $templateData['numbAmenities'] = count($amenity); + $templateData['numbAmenityMembers'] = $numbAmenityMembers; + $templateData['numbImagesFound'] = $numbImagesFound; + $templateData['numbCcards'] = count($ccard); + $templateData['numbCcardMembers'] = $numbCcardMembers; + + // For testing only + $templateData['member'] = $member; + $templateData['defaultMemberType'] = $defaultMemberType; + $templateData['amenity'] = $amenity; + $templateData['membAmen'] = $membAmen; + $templateData['category'] = $category; + $templateData['membCat'] = $membCat; + $templateData['catTrans'] = $catTrans; + $templateData['city'] = $city; + $templateData['state'] = $state; + $templateData['region'] = $region; + $templateData['ccard'] = $ccard; + $templateData['membCcard'] = $membCcard; + $templateData['image'] = $image; + } + + + if ($failure) { + return array( + 'status' => true, + 'menuItemRedirect' => 'management', + 'modelRedirect' => 'import', + 'view' => 'admin/management/import.html', + 'data' => $templateData + ); + + } + + $requestedView = 'import/readDatabase.html'; + + break; + + case 'importImages': + + require_once(GLM_MEMBERS_PLUGIN_PATH.'/models/admin/ajax/imageUpload.php'); + $ImageUpload = new GlmMembersAdmin_ajax_imageUpload($this->wpdb, $this->config); + + $refType = $this->config['ref_type_numb']['MemberInfo']; + $refTable = $this->config['ref_type_table'][$refType]; + + // Get image array stored in a WordPress option + $imageBaseURL = get_option( 'glm-member-db-import-imageurl', false ); + $image = get_option( 'glm-member-db-import-image', false ); + + delete_option( 'glm-member-db-import-imageurl', false ); + delete_option( 'glm-member-db-import-image', false ); + + // If we have image URLs + if ($image != false) { + + // For each member + foreach ($image as $m) { + + // Import member logo + if (isset($m['logo'])) { + $imageURL = $imageBaseURL.$m['logo']; + $res = $ImageUpload->storeImage ($imageURL); + + // If we got a good new filename back, then it should be a good image store + if ($res['newFileName']) { + + // Update the member record with the logo image name + $res = $this->wpdb->update( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'member_info', + array( + 'logo' => $res['newFileName'] + ), + array( 'id' => $m['new_memberinfo_id'] ), + array( '%s' ), + array( '%d' ) + ); + } + } + + // For each image in this member's gallery + if ($m['images']) { + foreach ($m['images'] as $i) { + $imageURL = $imageBaseURL.$i['image']; + $res = $ImageUpload->storeImage ($imageURL, $refType, $refTable, $m['new_memberinfo_id'], $i['caption']); + } + } + + } + + } + + $requestedView = 'import/importImages.html'; + + default: + + break; + + } + + // Return status, suggested view, and data to controller + return array( + 'status' => true, + 'menuItemRedirect' => false, + 'modelRedirect' => false, + 'view' => 'admin/management/'.$requestedView, + 'data' => $templateData + ); + + } + + /** + * Delete Members Database Tables + * + * @param string $dbVersion Current version of Members Database + * + * @return boolean + * @access public + */ + public function deleteDataTables($dbVersion) + { + + // Read in Database deletion script - assumes the current db version. + $sqlFile = GLM_MEMBERS_PLUGIN_DB_SCRIPTS.'/drop_database_V'.$dbVersion.'.sql'; + $sql = file_get_contents($sqlFile); + + // Replace {prefix} with table name prefix + $sql = str_replace('{prefix}', GLM_MEMBERS_PLUGIN_DB_PREFIX, $sql); + + if (GLM_MEMBERS_PLUGIN_ADMIN_DEBUG) { + glmMembersAdmin::addNotice('Dropping all database tables', 'Process'); + glmMembersAdmin::addNotice($sql, 'DataBlock', 'Drop Tables SQL'); + } + + // Removing the tables using the script + $this->wpdb->query($sql); + + // If there's been an error, display in debug Alert + $queryError = $this->wpdb->last_error; + if ($queryError) { + glmMembersAdmin::addNotice('Error when deleting database: Database tables may not have existed.
    Check following message.
    '.$queryError, 'AdminError'); + } + + return true; + + } + + /** + * Create Members Database Tables + * + * @param string $dbVersion Current version of Members Database + * + * @return boolean + * @access public + */ + public function createDataTables($dbVersion) + { + + // Read in Database creation script + $sqlFile = GLM_MEMBERS_PLUGIN_DB_SCRIPTS.'/create_database_V'.$dbVersion.'.sql'; + $sql = file_get_contents($sqlFile); + + // Replace {prefix} with table name prefix + $sql = str_replace('{prefix}', GLM_MEMBERS_PLUGIN_DB_PREFIX, $sql); + + // Split script into separate queries by looking for lines with only "---" + $queries = preg_split('/^----$/m', $sql); + + // Try executing all queries to build database + $qForDebug = ''; + do { + $q = current($queries); + $this->wpdb->query($q); + $queryError = $this->wpdb->last_error; + + if ($queryError) { + glmMembersAdmin::addNotice('Error when creating database: Database tables may already exist.
    Check following message.
    '.$queryError, 'AdminError'); + return false; + } + $qForDebug .= $queryError.$q; + + } while ($queryError == '' && next($queries)); + + if (GLM_MEMBERS_PLUGIN_ADMIN_DEBUG) { + glmMembersAdmin::addNotice('Creating database tables', 'Process'); + glmMembersAdmin::addNotice($qForDebug, 'DataBlock', 'Create Tables SQL'); + } + + return true; + + } + +} + +?> diff --git a/models/admin/management/import/memberImages.php b/models/admin/management/import/memberImages.php new file mode 100644 index 00000000..069beb18 --- /dev/null +++ b/models/admin/management/import/memberImages.php @@ -0,0 +1,58 @@ +wpdb, $this->config); + +$refType = $this->config['ref_type_numb']['MemberInfo']; +$refTable = $this->config['ref_type_table'][$refType]; + +// Get image array stored in a WordPress option +$imageBaseURL = get_option( 'glm-member-db-import-imageurl', false ); +$image = get_option( 'glm-member-db-import-image', false ); + +delete_option( 'glm-member-db-import-imageurl', false ); +delete_option( 'glm-member-db-import-image', false ); + +// If we have image URLs +if ($image != false) { + + // For each member + foreach ($image as $m) { + + // Import member logo + if (isset($m['logo'])) { + $imageURL = $imageBaseURL.$m['logo']; + $res = $ImageUpload->storeImage ($imageURL); + + // If we got a good new filename back, then it should be a good image store + if ($res['newFileName']) { + + // Update the member record with the logo image name + $res = $this->wpdb->update( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'member_info', + array( + 'logo' => $res['newFileName'] + ), + array( 'id' => $m['new_memberinfo_id'] ), + array( '%s' ), + array( '%d' ) + ); + } + } + + // For each image in this member's gallery + if ($m['images']) { + foreach ($m['images'] as $i) { + $imageURL = $imageBaseURL.$i['image']; + $res = $ImageUpload->storeImage ($imageURL, $refType, $refTable, $m['new_memberinfo_id'], $i['caption']); + } + } + + } + +} + +$requestedView = 'import/memberImages.html'; diff --git a/models/admin/management/import/members.php b/models/admin/management/import/members.php new file mode 100644 index 00000000..bc67b885 --- /dev/null +++ b/models/admin/management/import/members.php @@ -0,0 +1,989 @@ + $dbServer, 'problem' => false); +if (!$dbServer || $dbServer == '' || $dbServer != trim($_REQUEST['dbServer'])) { + $templateData['dbServer']['problem'] = 'Server name or IP address was not provided or contained invalid characters.'; + $failure = true; +} + +// Check database port # +$dbPort = preg_replace("/[^0-9]+/", "", trim($_REQUEST['dbPort'])); +$templateData['dbPort'] = array('value' => $dbPort, 'problem' => false); +if (!$dbPort || $dbPort == '' || $dbPort != trim($_REQUEST['dbPort'])) { + $templateData['dbPort']['problem'] = 'Server port was not provided or is not a valid nummber.'; + $failure = true; +} + +// Check database name +$dbName = preg_replace("/[^a-zA-Z0-9_]+/", "", trim($_REQUEST['dbName'])); +$templateData['dbName'] = array('value' => $dbName, 'problem' => false); +if (!$dbName || $dbName == '' || $dbName != trim($_REQUEST['dbName'])) { + $templateData['dbName']['problem'] = 'Database name was not provided or is not valid for Postgres.'; + $failure = true; +} + +// Check database user +$dbUser = preg_replace("/[^a-zA-Z0-9_]+/", "", trim($_REQUEST['dbUser'])); +$templateData['dbUser'] = array('value' => $dbUser, 'problem' => false); +if (!$dbUser || $dbUser == '' || $dbUser != trim($_REQUEST['dbUser'])) { + $templateData['dbUser']['problem'] = 'Database user was not provided or is not valid for Postgres.'; + $failure = true; +} + +// Check Image URL +$dbImageURL = filter_var($_REQUEST['dbImageURL'], FILTER_SANITIZE_URL); +$templateData['dbImageURL'] = array('value' => $dbImageURL, 'problem' => false); +if (!$dbImageURL || $dbImageURL == '' || $dbImageURL != trim($_REQUEST['dbImageURL'])) { + $templateData['dbImageURL']['problem'] = 'Image URL does not appear to be valid.'; + $failure = true; +} + +if ($failure) { + $templateData['genError'] = 'There was a problem with the database connection information you provided. See below for specific instructions.'; +} + +/* + * Determine if source database is sane + */ + +// Connect to database +if (!$failure) { + $connString = "host=$dbServer port=$dbPort dbname=$dbName user=$dbUser"; + $db = @pg_connect($connString); + if (!$db) { + + $err = error_get_last(); + $templateData['genError'] = 'There was a problem connecting to the database. The error message was...
    '.$err['message']; + $failure = true; + + } +} + +// Determine if the members schema exists +if (!$failure) { + $sql = " + SELECT EXISTS + ( + SELECT 1 + FROM information_schema.schemata AS exists + WHERE schema_name = 'members' + ) AS isMembers + ;"; + $res = pg_query($db, $sql); + if (pg_fetch_result($res, 0, 'isMembers') == 'f') { + $templateData['genError'] = 'The "members" schema was not found! Is this the right database?'; + $failure = true; + } +} + +/* + * Load data from source database + */ + +// Attempt to get member base data +if (!$failure) { + $sql = " + SELECT * + FROM members.member + ORDER BY member_id + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any members listed in this database!'; + $failure = true; + } else { + $member = pg_fetch_all($res); + if (count($member) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading base member data, we did not receive the expected number of members! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } + } +} + +// Read in all amenities +if (!$failure) { + $sql = " + SELECT * + FROM members.amenity + ORDER BY amenity_name + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any cities listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading amenity data, we did not receive the expected number of amenities! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $amenity = array(); + + // Reprocess into array indexed by amenity_id + foreach ($tmp as $x) { + $amenity[$x['amenity_id']] = $x; + } + } + } +} + +// Read in member/amenity cross-reference table +$numbAmenityMembers = 0; +if (!$failure) { + $sql = " + SELECT * + FROM members.member_amenity + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any member/amenity cross-reference entries listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading amenity data, we did not receive the expected number of amenities! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the ameities into an array indexed by amenity_id + $membAmen = array(); + + // Reprocess into array grouped by member with the main index the member ID + foreach ($tmp as $x) { + + // If member entry hasn't been created yet, add it now + if (!isset($membAmen[$x['member_id']])) { + $membAmen[$x['member_id']] = array(); + } + + $membAmen[$x['member_id']][] = $x; + $numbAmenityMembers++; + } + } + } +} + +// Read in all credit card types +if (!$failure) { + $sql = " + SELECT * + FROM members.ccard_type + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any credit card types listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading credit card type data, we did not receive the expected number of credit card types! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $ccard = array(); + + // Reprocess into array indexed by ccard_type_id + foreach ($tmp as $x) { + $ccard[$x['ccard_type_id']] = $x; + } + } + } +} + +// Read in member/credit card cross-reference table +$numbCcardMembers = 0; +if (!$failure) { + $sql = " + SELECT * + FROM members.member_ccard_type + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any member/credit card cross-reference entries listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading member/credit card cross-reference data, we did not receive the expected number of entries! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $membCcard = array(); + + // Reprocess into array grouped by member with the main index the member ID + foreach ($tmp as $x) { + + // If member entry hasn't been created yet, add it now + if (!isset($membCcard[$x['member_id']])) { + $membCcard[$x['member_id']] = array(); + } + + $membCcard[$x['member_id']][] = $x; + $numbCcardMembers++; + } + } + } +} + + +// Read in all member categories +$haveCatImportIssues = false; +$catImportIssues = ''; +if (!$failure) { + $sql = " + SELECT * + FROM members.category + ORDER BY category_id + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any categories listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading category data, we did not receive the expected number of categories! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the categories into a higherarchical array + $category = array(); + + // Process top level categories first + foreach ($tmp as $x) { + + // If this is a top level category - parent = 0 or points to itself + if ($x['parent_id'] == 0 || $x['parent_id'] == $x['category_id']) { + + foreach ($category as $y) { + if ($y['name'] == $x['name']) { + $haveCatImportIssues = true; + $catImportIssues .= '
  • Category '.$x['name'].' ('.$x['category_id'].') has the same name as another category at the same level. Added as duplicate.
  • '; + } + } + reset($category); + + $category[$x['category_id']] = $x; + $category[$x['category_id']]['subcat'] = array(); + + if ($x['parent_id'] == $x['category_id']) { + $haveCatImportIssues = true; + $catImportIssues .= '
  • Category '.$x['name'].' ('.$x['category_id'].') was pointing to itself as a parent. Now a top level category.
  • '; + } + } + + } + + // Now look for sub-categories with bad parents and add them as a main category + reset($tmp); + foreach ($tmp as $x) { + + // If this is NOT a top level category - Parent not 0 and not pointing to itself + if ($x['parent_id'] > 0 && $x['parent_id'] != $x['category_id']) { + + // Check if we don't have the requested parent as a top-level category + if (!isset($category[$x['parent_id']])) { + // Since the parent doesn't exist, we're just going to make this one a parent. + $haveCatImportIssues = true; + $catImportIssues .= '
  • Category '.$x['name'].' ('.$x['category_id'].') had a bad parent ID ('.$x['parent_id'].'). Now a top level category.
  • '; + $category[$x['category_id']] = $x; + $category[$x['category_id']]['subcat'] = array(); + } + } + } + + // Now process all supposedly good sub-categories + reset($tmp); + foreach ($tmp as $x) { + + // If this is NOT a top level category - Parent not 0 and not pointing to itself + if ($x['parent_id'] > 0 && $x['parent_id'] != $x['category_id']) { + + // Check if we have the requested parent as a top-level category + if (isset($category[$x['parent_id']])) { + $category[$x['parent_id']]['subcat'][$x['category_id']] = $x; + } + } + } + } + } +} + +// Read in member/Category cross-reference table +if (!$failure) { + $sql = " + SELECT * + FROM members.member_category + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any member/category cross-reference entries listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading category data, we did not receive the expected number of categories! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $membCat = array(); + + // Reprocess into array grouped by member with the main index the member ID + foreach ($tmp as $x) { + + // If member entry hasn't been created yet, add it now + if (!isset($membCat[$x['member_id']])) { + $membCat[$x['member_id']] = array(); + } + + $membCat[$x['member_id']][] = $x; + } + } + } +} + +// Read in all cities +if (!$failure) { + $sql = " + SELECT * + FROM members.city + ORDER BY city_name + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any cities listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading city data, we did not receive the expected number of cities! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $city = array(); + + // Reprocess into array indexed by city_id + foreach ($tmp as $x) { + $city[$x['city_id']] = $x; + } + } + } +} + +// Read in all states +if (!$failure) { + $sql = " + SELECT * + FROM members.state + ORDER BY state_name + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any states listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading state data, we did not receive the expected number of states! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $state = array(); + + // Reprocess into array indexed by state_id + foreach ($tmp as $x) { + $state[$x['state_id']] = $x; + } + } + } +} + +// Read in all regions +if (!$failure) { + $sql = " + SELECT * + FROM members.region + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any regions listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading region data, we did not receive the expected number of regions! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $region = array(); + + // Reprocess into array indexed by state_id + foreach ($tmp as $x) { + $region[$x['region_id']] = $x; + } + } + } +} + +// Read in all member photos for member image gallery +$numbImagesFound = 0; +if (!$failure) { + $sql = " + SELECT * + FROM members.member_photos + ;"; + $res = pg_query($db, $sql); + $rows = pg_num_rows($res); + if ($rows == 0) { + $templateData['genError'] = 'There does not appear to be any member photos listed in this database!'; + $failure = true; + } else { + $tmp = pg_fetch_all($res); + if (count($tmp) != $rows) { + $notice = pg_last_notice($res); + $templateData['genError'] = 'While reading member photo data, we did not receive the expected number of member photos! '; + if ($notice) { + $templateData['genError'] .= 'Perhaps the following message will help.
    '.$notice; + } + $failure = true; + } else { + + // Since there was no problem, we'll post-process the cities into an array indexed by city_id + $image = array(); + + // Reprocess into array grouped by member with the main index the member ID + foreach ($tmp as $x) { + + // If member entry hasn't been created yet, add it now + if (!isset($image[$x['member_id']])) { + $image[$x['member_id']] = array( + 'member_id' => $x['member_id'], + 'images' => array() + ); + } + + $image[$x['member_id']]['images'][] = $x; + $numbImagesFound++; + } + } + } +} + +/* + * If requested, reset the database + */ + +// Reset database is Option is selected +if (!$failure && isset($_REQUEST['dbReset']) && $_REQUEST['dbReset'] == 'on') { + + // Get current db version + $dbVersion = GLM_MEMBERS_PLUGIN_DB_VERSION; + + // Reset the database + if (!$this->deleteDataTables($dbVersion)) { + glmMembersAdmin::addNotice('Unable to delete the database tables while resetting the database.
    ', 'AdminError'); + break; + } + if (!$this->createDataTables($dbVersion)) { + glmMembersAdmin::addNotice('Unable to create the database tables while resetting the database.
    ', 'AdminError'); + break; + } + + glmMembersAdmin::addNotice('Database tables have been reset in preparation importing members.
    ', 'AdminNotice'); + + // Delete Images + foreach( new RecursiveIteratorIterator( + + new RecursiveDirectoryIterator( GLM_MEMBERS_PLUGIN_IMAGES_PATH, FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS ), + RecursiveIteratorIterator::CHILD_FIRST ) as $value ) { + if ($value->isFile()) { + unlink( $value ); + } + } + +} + +/* + * Start importing data + */ + +if (!$failure) { + + // Import Cities + while (list ($key, $val) = each ($city) ) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'cities', + array( + 'name' => $val['city_name'], + ), + array( + '%s', + ) + ); + $city[$key]['new_id'] = $this->wpdb->insert_id; + + } + + // Import Regions + while (list ($key, $val) = each ($region) ) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'regions', + array( + 'name' => $val['region_name'], + 'descr' => '', + 'short_descr' => '' + ), + array( + '%s', + '%s', + '%s' + ) + ); + $region[$key]['new_id'] = $this->wpdb->insert_id; + + } + + // Import Amenities + while (list ($key, $val) = each ($amenity) ) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'amenities', + array( + 'active' => true, + 'name' => $val['amenity_name'], + 'descr' => '', + 'short_descr' => '', + 'ref_type' => $this->config['ref_type_numb']['MemberInfo'] + ), + array( + '%d', + '%s', + '%s', + '%s', + '%d' + ) + ); + $amenity[$key]['new_id'] = $this->wpdb->insert_id; + + } + + // Import Categories + $catTrans = array(); + $numbCategories = 0; + while (list ($key, $val) = each ($category) ) { + + $numbCategories++; + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'categories', + array( + 'name' => $val['name'], + 'descr' => '', + 'short_descr' => '', + 'parent' => 0 + ), + array( + '%s', + '%s', + '%s', + '%d' + ) + ); + $newID = $this->wpdb->insert_id; + $category[$key]['new_id'] = $newID; + $category[$key]['new_parent'] = 0; + + $catTrans[$key] = array( + 'new' => $newID, + 'parent' => 0 + ); + + // Do any sub-cats for this category + while (list ($key2, $val2) = each ($val['subcat']) ) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'categories', + array( + 'name' => $val2['name'], + 'descr' => '', + 'short_descr' => '', + 'parent' => $category[$key]['new_id'] + ), + array( + '%s', + '%s', + '%s', + '%d' + ) + ); + $newID2 = $this->wpdb->insert_id; + $category[$key]['subcat'][$key2]['new_id'] = $newID; + $category[$key]['subcat'][$key2]['new_parent'] = $category[$key]['new_id']; + + $catTrans[$key2] = array( + 'new' => $newID2, + 'parent' => $newID + ); + + } + } + + + + // Index Credit Card types - Match to card types in configuration + while (list ($key, $val) = each ($ccard) ) { + reset($this->config['credit_card_numb']); + while (list ($key2, $val2) = each ($this->config['credit_card_numb']) ) { + if (substr($val['ccard_type_name'], 0, 3) == substr(($key2), 0, 3)) { + $ccard[$key]['bitpos'] = $val2; + } + } + } + + /* + * Create Defaults Not Provided By Old Member DB + */ + + // Member Types + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'member_type', + array( + 'name' => 'Default', + 'descr' => '', + ), + array( + '%s', + '%s', + ) + ); + $defaultMemberType = $this->wpdb->insert_id; + + /* + * Now Start Adding Members + */ + + // Import Members + $numbMembersActive = 0; + $numbMembersInactive = 0; + $namesInserted = array(); + $membImportIssues = ''; + $haveMembImportIssues = false; + $dupeNames = 0; + while (list ($key, $val) = each ($member) ) { + + // Determine if member is active and set access accordingly + if ($val['active'] == 't') { + + // Member is active, so set to active-moderated + $access = $this->config['access_numb']['Moderated']; + $numbMembersActive++; + + } else { + + // Member is not active, so set to no display no access + $access = $this->config['access_numb']['NotDisplayed']; + $numbMembersInactive++; + + } + + // Check for duplicate name + $membName = $val['member_name']; + if (isset($namesInserted[$membName])) { + + // Bump dupe count and add to name to make this one unique + $dupeNames++; + $membName .= ' DUPE-'.$dupeNames; + + $membImportIssues .= '
  • Member Duplicate '.$membName.'
  • '; + $haveMembImportIssues = true; + + } + + // Add main member record + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'members', + array( + 'access' => $access, + 'member_type' => $defaultMemberType, + 'created' => date('Y-m-d'), + 'name' => $membName, + 'member_slug' => sanitize_title($val['member_name']), + 'old_member_id' => $val['member_id'] + ), + array( + '%d', + '%d', + '%s', + '%s', + '%s', + '%d' + ) + ); + $membID = $this->wpdb->insert_id; + $member[$key]['new_id'] = $membID; + + // Add this member to the names inserted so we can check for duplicates + $namesInserted[$membName] = true; + + // Create truncated short_descr from descritions - Less tags, html encoded characters, newlines, tabs, etc. + $stripped = str_replace(PHP_EOL, '', preg_replace('/\t+/', '', preg_replace("/&#?[a-z0-9]{2,8};/i", "", strip_tags($val['description'])))); + $short_descr = implode(' ', array_slice(explode(' ', $stripped), 0, 30)); + + if (strlen($short_descr) < strlen($stripped)) { + $short_descr .= ' ...'; + } + + // Build Credit Card bitmap + $ccBits = 0; + if (isset($membCcard[$val['member_id']])) { + foreach ($membCcard[$val['member_id']] as $c) { + $b = $ccard[$c['ccard_type_id']]['bitpos']; + $ccBits += pow(2, $b); + } + } + + // Insert Member Information Record + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'member_info', + array( + 'member' => $membID, + 'member_name' => $val['member_name'], + 'status' => $this->config['status_numb']['Active'], + 'reference_name' => 'Imported Member Information', + 'descr' => $val['description'], + 'short_descr' => $short_descr, + 'addr1' => $val['street'], + 'addr2' => '', + 'city' => $city[$val['city_id']]['new_id'], + 'state' => $state[$val['state_id']]['state_abb'], + 'country' => 'US', + 'zip' => $val['zip'], + 'lat' => $val['lat'], + 'lon' => $val['lon'], + 'region' => (isset($region[$val['region']]) ? $region[$val['region']]['new_id'] : 0), + 'phone' => $val['phone'], + 'toll_free' => $val['toll_free'], + 'url' => $val['url'], + 'email' => $val['process_email'], + 'logo' => '', + 'cc_type' => $ccBits, + 'notes' => '', + 'create_time' => $val['create_date'], + 'modify_time' => $val['last_update'] + ), + array( + '%d', + '%s', + '%d', + '%s', + '%s', + '%s', + '%s', + '%s', + '%d', + '%s', + '%s', + '%s', + '%f', + '%f', + '%d', + '%s', + '%s', + '%s', + '%s', + '%s', + '%d', + '%s', + '%s', + '%s' + ) + ); + $infoID = $this->wpdb->insert_id; + $member[$key]['new_info_id'] = $infoID; + + // Add Member Categories + if (isset($membCat[$val['member_id']])) { + foreach ($membCat[$val['member_id']] as $c) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'category_member_info', + array( + 'category' => $catTrans[$c['category_id']]['new'], + 'member_info' => $infoID + ), + array( + '%d', + '%d' + ) + ); + + } + } + + // Add Member Amenities + if (isset($membAmen[$val['member_id']])) { + foreach ($membAmen[$val['member_id']] as $a) { + + $res = $this->wpdb->insert( + GLM_MEMBERS_PLUGIN_DB_PREFIX.'amenity_ref', + array( + 'amenity' => $amenity[$a['amenity_id']]['new_id'], + 'ref_type' => $this->config['ref_type_numb']['MemberInfo'], + 'ref_dest' => $infoID + ), + array( + '%d', + '%d', + '%d' + ) + ); + + } + } + + // Add logo to image array + if ($val['logo'] != '') { + // If member entry hasn't been created yet, add it now + if (!isset($image[$val['member_id']])) { + $image[$val['member_id']] = array( + 'member_id' => $val['member_id'], + 'images' => false + ); + } + $image[$val['member_id']]['logo'] = $val['logo']; + } + + // Update image list with new member IDs + if (isset($image[$val['member_id']])) { + $image[$val['member_id']]['new_memberinfo_id'] = $infoID; + } + + } + + // Import + +} + +// If everything is OK, make data available to the template +if (!$failure) { + + update_option( 'glm-member-db-import-imageurl', $dbImageURL); + update_option( 'glm-member-db-import-image', $image ); + + $templateData['numbCities'] = count($city); + $templateData['numbStates'] = count($state); + $templateData['numbRegions'] = count($region); + $templateData['numbMembers'] = count($member); + $templateData['numbMembersActive'] = $numbMembersActive; + $templateData['numbMembersInactive'] = $numbMembersInactive; + $templateData['haveMembImportIssues'] = $haveMembImportIssues; + $templateData['membImportIssues'] = $membImportIssues; + $templateData['numbCategories'] = count($catTrans); + $templateData['haveCatImportIssues'] = $haveCatImportIssues; + $templateData['catImportIssues'] = $catImportIssues; + $templateData['numbCategoryMembers'] = count($membCat); + $templateData['numbAmenities'] = count($amenity); + $templateData['numbAmenityMembers'] = $numbAmenityMembers; + $templateData['numbImagesFound'] = $numbImagesFound; + $templateData['numbCcards'] = count($ccard); + $templateData['numbCcardMembers'] = $numbCcardMembers; + + // For testing only + $templateData['member'] = $member; + $templateData['defaultMemberType'] = $defaultMemberType; + $templateData['amenity'] = $amenity; + $templateData['membAmen'] = $membAmen; + $templateData['category'] = $category; + $templateData['membCat'] = $membCat; + $templateData['catTrans'] = $catTrans; + $templateData['city'] = $city; + $templateData['state'] = $state; + $templateData['region'] = $region; + $templateData['ccard'] = $ccard; + $templateData['membCcard'] = $membCcard; + $templateData['image'] = $image; +} + + +if ($failure) { + return array( + 'status' => true, + 'menuItemRedirect' => 'management', + 'modelRedirect' => 'import', + 'view' => 'admin/management/import.html', + 'data' => $templateData + ); + +} + +$requestedView = 'import/members.html'; + diff --git a/models/admin/member/memberInfo.php b/models/admin/member/memberInfo.php index f04f3e5f..5ab5b792 100644 --- a/models/admin/member/memberInfo.php +++ b/models/admin/member/memberInfo.php @@ -316,25 +316,24 @@ class GlmMembersAdmin_member_memberInfo extends GlmDataMemberInfo if ($this->haveMemberInfo) { // Update the member Info data - $this->memberInfo = $this->updateEntry($this->memberInfoID, 'id', true, true); + $this->memberInfo = $this->updateEntry($this->memberInfoID, 'id', true); + if ($this->memberInfo['status']) { $memberUpdated = true; } else { $memberUpdateError = true; } - break; - } else { if (GLM_MEMBERS_PLUGIN_ADMIN_DEBUG) { glmMembersAdmin::addNotice("  No member information exists, assuming this is a new submission.", 'Process'); } - // Note that if we don't have an existing member info record, we fall through to "addNew" below. - } + break; + // Add the new member information record case 'addNew': diff --git a/setup/databaseScripts/create_database_V1.1.1.sql b/setup/databaseScripts/create_database_V1.1.2.sql similarity index 95% rename from setup/databaseScripts/create_database_V1.1.1.sql rename to setup/databaseScripts/create_database_V1.1.2.sql index a47fc1b7..0d2d1b53 100644 --- a/setup/databaseScripts/create_database_V1.1.1.sql +++ b/setup/databaseScripts/create_database_V1.1.2.sql @@ -123,6 +123,25 @@ CREATE TABLE {prefix}contacts ( ---- +-- Files - Files are stored under /wp-content/uploads/glm-member-db/files/ +CREATE TABLE {prefix}files ( + id INT NOT NULL AUTO_INCREMENT, + name TINYTEXT NULL, -- Original name of the file - might be URL if copied via HTTP + status TINYINT(1) NULL, -- Display/Use status - See plugin.ini status table + file_name TINYTEXT NULL, -- Stored file name for the file + descr TEXT NULL, -- Description + position INT NULL, -- Numeric position for sequence of display + ref_type INT NULL, -- Type of entity this image is associated with + ref_dest INT NULL, -- Pointer to the specific entity of ref_type this image is associated with + PRIMARY KEY (id), + INDEX(name(20)), + INDEX(file_name(20)), + INDEX(ref_type), + INDEX(ref_dest) +); + +---- + -- Images - Images are stored under /wp-content/uploads/glm-member-db/images/{size}/ CREATE TABLE {prefix}images ( id INT NOT NULL AUTO_INCREMENT, diff --git a/setup/databaseScripts/dbVersions.php b/setup/databaseScripts/dbVersions.php index 5c9e5f50..1da28f88 100644 --- a/setup/databaseScripts/dbVersions.php +++ b/setup/databaseScripts/dbVersions.php @@ -16,6 +16,9 @@ /** * Database Versions * + * *** PLEASE NOW INCLUDE A DATE FOR EACH DATABASE VERSION *** + * '1.1.2' => array('version' => '1.1.2', 'tables' => 14, 'date' => '4/11/16') + * * An array of past and current Member Database versions. * * Each entry below uses a key so code can find data on @@ -29,7 +32,8 @@ $glmMembersDbVersions = array( '1.0.30' => array('version' => '1.0.30', 'tables' => 26), '1.0.43' => array('version' => '1.0.43', 'tables' => 26), '1.1.0' => array('version' => '1.1.0', 'tables' => 13), - '1.1.1' => array('version' => '1.1.1', 'tables' => 13) + '1.1.1' => array('version' => '1.1.1', 'tables' => 13), + '1.1.2' => array('version' => '1.1.2', 'tables' => 14, 'date' => '4/11/16') ); diff --git a/setup/databaseScripts/drop_database_V1.1.1.sql b/setup/databaseScripts/drop_database_V1.1.2.sql similarity index 96% rename from setup/databaseScripts/drop_database_V1.1.1.sql rename to setup/databaseScripts/drop_database_V1.1.2.sql index ffdb3fe0..78bacb57 100644 --- a/setup/databaseScripts/drop_database_V1.1.1.sql +++ b/setup/databaseScripts/drop_database_V1.1.2.sql @@ -11,6 +11,7 @@ DROP TABLE IF EXISTS {prefix}cities, {prefix}contacts, {prefix}images, + {prefix}files, {prefix}members, {prefix}member_info, {prefix}member_type, diff --git a/setup/databaseScripts/readme.txt b/setup/databaseScripts/readme.txt new file mode 100644 index 00000000..141d8b5b --- /dev/null +++ b/setup/databaseScripts/readme.txt @@ -0,0 +1,41 @@ +This directory contains database creation and update scripts for this add-on. + +The files in this directory are checked by the checkDatabase() function in the +main plugin classes/glmPluginSupport.php file. + +This directory is optional. If there are no data tables that need to be created +for this add-on, there should be no files in this directory. The directory may +also be deleted. + +See the "examples" directory for a sample of what can go in this directory. +Procedure to update database +----------------------------- + +0) Make a backup copy of the site's database. + +1) Rename "create_database_Vx.x.x.sql" to new version number. + example: create_database_V0.0.9.sql -> create_database_V0.0.10.sql + +2) Edit renamed create database file and make desired changes + +3) Add a new "update_database_Vx.x.x.sql" named with the correct version #. + +4) Edit new update database files with SQL script to make the necessary changes + from the previous version to the new version. (i.e. to add new fields, + rename fields, insert records, ...) + +5) Optionally add an "update_database_Vx.x.x.php" file if PHP scripting is + needed to update database content. (i.e. to make changes to database content) + +6) Edit the "dbVersions.php" file and add a new line for the new version. + *** Now please be sure to add a date for each entry *** + i.e. '1.1.2' => array('version' => '1.1.2', 'tables' => 14, 'date' => '4/11/16') + +7) When this is all done, edit the index.php file for the plugin/add-on and + change "GLM_MEMBERS_{addon}_PLUGIN_DB_VERSION" defined parameter where + {addon} is the add-on name. + +8) Go to an admin menu item for the main member db plugin or any add-on. If all + goes well, the main plugin should have detected the change and updated the + database. If not, restore the database and try again. +9) Check the database to make sure the changes to fields and data are correct. diff --git a/setup/databaseScripts/update_database_V1.0.30.php b/setup/databaseScripts/update_database_V1.0.30.php index 8d4b8964..3bcccc09 100644 --- a/setup/databaseScripts/update_database_V1.0.30.php +++ b/setup/databaseScripts/update_database_V1.0.30.php @@ -2,7 +2,7 @@ /* * Gaslight Media Members Database * - * Database Update Script for version 1.0.28 + * Database Update Script for version 1.0.30 */ // Update member_info records with member name slug for URLs diff --git a/setup/databaseScripts/update_database_V1.0.30.sql b/setup/databaseScripts/update_database_V1.0.30.sql index fde35512..35642c19 100644 --- a/setup/databaseScripts/update_database_V1.0.30.sql +++ b/setup/databaseScripts/update_database_V1.0.30.sql @@ -1,6 +1,6 @@ -- Gaslight Media Members Database -- File Created: 12/09/14 15:27:15 --- Database Version: 1.0.28 +-- Database Version: 1.0.30 -- Database Update From Previous Version Script -- -- To permit each query below to be executed separately, diff --git a/setup/databaseScripts/update_database_V1.0.43.sql b/setup/databaseScripts/update_database_V1.0.43.sql index 921f4bd4..b5588235 100644 --- a/setup/databaseScripts/update_database_V1.0.43.sql +++ b/setup/databaseScripts/update_database_V1.0.43.sql @@ -1,6 +1,6 @@ -- Gaslight Media Members Database -- File Created: 12/09/14 15:27:15 --- Database Version: 1.0.28 +-- Database Version: 1.0.43 -- Database Update From Previous Version Script -- -- To permit each query below to be executed separately, diff --git a/setup/databaseScripts/update_database_V1.1.0.sql b/setup/databaseScripts/update_database_V1.1.0.sql index a20e7c58..03332821 100644 --- a/setup/databaseScripts/update_database_V1.1.0.sql +++ b/setup/databaseScripts/update_database_V1.1.0.sql @@ -1,6 +1,6 @@ -- Gaslight Media Members Database -- File Created: 12/09/14 15:27:15 --- Database Version: 1.0.28 +-- Database Version: 1.1.0 -- Database Update From Previous Version Script -- -- To permit each query below to be executed separately, diff --git a/setup/databaseScripts/update_database_V1.1.1.sql b/setup/databaseScripts/update_database_V1.1.1.sql index 6d397416..0ed653dc 100644 --- a/setup/databaseScripts/update_database_V1.1.1.sql +++ b/setup/databaseScripts/update_database_V1.1.1.sql @@ -1,6 +1,6 @@ -- Gaslight Media Members Database -- File Created: 12/09/14 15:27:15 --- Database Version: 1.0.28 +-- Database Version: 1.1.1 -- Database Update From Previous Version Script -- -- To permit each query below to be executed separately, diff --git a/setup/databaseScripts/update_database_V1.1.2.php b/setup/databaseScripts/update_database_V1.1.2.php new file mode 100644 index 00000000..50f8892f --- /dev/null +++ b/setup/databaseScripts/update_database_V1.1.2.php @@ -0,0 +1,13 @@ + - + diff --git a/views/admin/management/import/importImages.html b/views/admin/management/import/memberImages.html similarity index 100% rename from views/admin/management/import/importImages.html rename to views/admin/management/import/memberImages.html diff --git a/views/admin/management/import/readDatabase.html b/views/admin/management/import/members.html similarity index 97% rename from views/admin/management/import/readDatabase.html rename to views/admin/management/import/members.html index c96d13b2..39053854 100644 --- a/views/admin/management/import/readDatabase.html +++ b/views/admin/management/import/members.html @@ -49,7 +49,7 @@ {/if} - +

    WARNING: This process may take a very long time!
    Do not interrupt or re-submit this page.

    diff --git a/views/admin/member/memberInfo.html b/views/admin/member/memberInfo.html index 7fd15c48..651a68e2 100644 --- a/views/admin/member/memberInfo.html +++ b/views/admin/member/memberInfo.html @@ -131,7 +131,7 @@
    {php} wp_editor('{$memberInfo.fieldData.descr|escape:quotes}', 'glm_descr', array( - // 'media_buttons' => true, + 'media_buttons' => false, // 'quicktags' => false, // 'wpautop' => false, NOTE: Dont's use. Problem when numerous spaces before text. 'textarea_name' => 'descr', -- 2.17.1