'; echo ''; echo ''; echo 'Merchant panel on ChurchMapped'; echo ' '; echo ' '; echo ' '; echo ' '; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
'; # Don't forget that for prices entered into the merchant upload panel, we have to multiply by 100 internally before storing it in the list_of_products table in the database churchma_PRODUCTS_OF_MERCHANTS. For example, if a merchant enters £1.20, we store it internally as 120 (1.20 * 100 = 120). This is because of Barclays ePDQ purposes. # We use parameterised queries here. # Our code should be nimble enough to detect whether the user has used a comma, and for what purpose? In European countries, prices are usually represented as - for example - 1,00 whereas in the UK the equivalent would be 1.00 . Furthermore, in the UK, one-thousand pounds is represented as 1,000.00 but in Europe it would be 1.000,00. Even though as of 15th July 2021, we only accept British currency (GBP), European users or those from a European background might still write numbers in this way. The simplest approach to this is to declare a statement in the form to the effect requesting British users to not use punctuation *at all*. E.g. 1000 is acceptable but neither is 1,000 or 1.000. This gets around a number of issues. We should also communicate to users not to enter currencies into the field. # UPDATE 16th July 2021: I just stupidly realised that not using punctuation at all wouldn't be able to encompass values like 4.99 (four pounds and ninety-nine pence). So to get around this, what we do is *insist* to the end user that they must state prices in the format used in Britain. Furthermore, we use the input type in HTML designated "number" which only accepts numbers. # In future versions, we might accept fields for languages other than English.This would require configuring the list_of_products table to have more columns to accommodate the various translations. We would also have to accommodate our form as well. # These are the various business subscriptions ChurchMapped is selling as of 16th JULY 2021 (product id is in brackets) : # BUSINESS SUBSCRIPTION - 1 MONTH : £14.99 + VAT per month (1) # BUSINESS SUBSCRIPTION - 3 MONTHS: £11.99 + VAT per month (2) # BUSINESS SUBSCRIPTION - 6 - MONTHS: £7.99 + VAT per month (3) # BUSINESS SUBSCRIPTION - 1 YEAR: £4.99 + VAT per month (4) # Note that the amounts are prepaid in advance, e.g. for the 3 months plan we enter 11.99 * 3 = 35.97 and then multiply by 100 for ePDQ purposes making 3597. This avoids having to periodically email the end user, which is quite complicated as far as ePDQ is concerned. Note that we do not include VAT into the database; this will be determined at checkout stage. # When using MySQL stored procedures, as always, use mysqli_next_result(). # It is possible in a future stage of ChurchMapped, we can further gain revenue by creating an API that taps into the Merchant Upload Panel. #We use this section to find out the details of a user's business subscription. $queryToFindBusinessSubscribersDetails = 'SELECT * FROM churchma_USERS_ON_CHURCHMAPPED.user_subscription_details WHERE (business_user_id_on_churchmapped = ' . (int)$_SESSION['sessionCHURCHMAPPEDUSERID'] . ' ' . 'AND TIMESTAMPDIFF(MICROSECOND, NOW() - INTERVAL 7 DAY, date_of_expiration_of_subscription) > 0) AND (product_id_of_subscription = 1 OR product_id_of_subscription = 2 OR product_id_of_subscription = 3 OR product_id_of_subscription = 4)'; #What we want to find here are the list of businesses who have paid their business subscription. $resultOfQueryToFindBusinessSubscribersDetails = mysqli_query($conn, $queryToFindBusinessSubscribersDetails); # We use this segment to prepare for check if the merchant user has already submitted 10 items. If they have, we prevent the merchant from adding more. mysqli_select_db($conn, "churchma_PRODUCTS_OF_MERCHANTS"); $dateOfToday = date("Y-m-d"); #dateOfToday gives the date of today according to the time on the server. The parameter "Y-m-d" gives the date in YYYY-MM-DD format. $queryToFindNumberOfItemsSubmittedByMerchantAlready = 'SELECT * FROM `list_of_products` WHERE DATE(timestamp_of_when_product_or_service_was_added) =" ' . $dateOfToday . ' " AND merchant_id =' . (int)$_SESSION['sessionCHURCHMAPPEDUSERID']; #We check to see the number of items the merchant user has submitted today already. $resultOfQueryToFindNumberOfItemsSubmittedByMerchantAlready = mysqli_query($conn, $queryToFindNumberOfItemsSubmittedByMerchantAlready); #This applies the mySQL query that attempts to see how many products the merchant has submitted this very day. if($_SESSION['sessionTYPEOFUSER'] != "BUSINESS"){ #Both $_SESSION['sessionTYPEOFUSER'] and $_SESSION['sessionCHURCHMAPPEDUSERID'] should already be set in the script including this file. die("Access forbidden. You must be a registered business on ChurchMapped to access this area."); #If the user is not on a Business account, we exit out of the script as they are forbidden here. Note that "!=" indicates "not equal to" (ignoring data type) whilst "!==" also considers data type. }elseif(mysqli_num_rows($resultOfQueryToFindBusinessSubscribersDetails) <= 0 && ($_SESSION['sessionTYPEOFUSER'] == "BUSINESS" && $_SESSION['sessionCHURCHMAPPEDUSERID'] != 1)){ #We have to add the qualifier $_SESSION['sessionTYPEOFUSER'] == "BUSINESS" && $_SESSION['sessionCHURCHMAPPEDUSERID'] != 1 otherwise the user ChurchMapped (which should always have an ID of 1) would have to be forced to make a purchase of a business subscription in order to upload items, which does not make sense. #We use this section to determine if the business user has paid their subscription fee. This is done by checking the table user_subscription_details in the database churchma_USERS_ON_CHURCHMAPPED. Within this table, we check the column date_of_expiration and see whether the merchant subscription today has passed the date_of_expiration. We can do this by using the TIMESTAMPDIFF function in MySQL. This takes three parameters: unit (e.g. SECOND, MINUTE, and so on), datetime expression 1, datetime expression 2. If the value returned is negative after using the following function, # SELECT TIMESTAMPDIFF(MICROSECOND, NOW() - INTERVAL 7 DAY, date_of_expiration) # Then it indicates that the subscription has expired and the functions should be disabled. We use INTERVAL 7 DAY (note that we subtract INTERVAL 7 DAY, not add) for the 7 day grace period. # To clarify, TIMESTAMPDIFF works by substracting the second parameter from the third parameter. If the result is negative, it indicates the date/time has passed. Otherwise, if it is positive, it indicates the date/time is still to come. We use microsecond as we have to be precise (using DAY, for example, would allow a user an extra day or so). # If the number of rows is 0 or less than 0 from $resultOfQueryToFindBusinessSubscribersDetails, this indicates that either the user has no subscription or whatever subscription they do have has ended. echo 'Sadly, you either do not have a subscription or your subscription has now ended. Please visit ' . ' ' . $churchmappedMarketplaceWebsite . '' . ' ' . 'in order to purchase a subscription'; # We use the following area to echo the form but with all the fields disabled. However, we do not echo a submit button. The effect of this is to show to a user what they could potentially be missing out on. } elseif(mysqli_num_rows($resultOfQueryToFindNumberOfItemsSubmittedByMerchantAlready) >= 10){ #Here we prevent the user from submitting additional items if they have already submitted 10 this day. If they have already submitted 10 items, echo 'Sadly, you have reached your daily limit of uploads for the day. Please try again tomorrow.'; } else{ #Place main code of form here. This form should ask the following questions: #"What is the name of the product or service you are providing?:" (The answer to this goes to product_name_english. We should only accept max 25 characters for this and no fewer than 0 characters). #"Please provide a description of the product or service you are providing?" (The answer to this goes to product_description_english. We should accept max 1000 characters (because the description should allow the merchant to give a fuller account of the item) and no fewer than 0 characters). # "How much does your item cost, excluding VAT (we will deal with that!)? Please provide a value that does not include the currency or decimal/comma separator. For example: 1000 , 400 , 300. Do NOT enter a value like £1.00 or 5,00." (The answer to this goes to product_price). # "What category would you say your item or service falls under? Please select from the option below:" (The answer to this goes to category_of_product_by_sic_classification. We can call our stored procedure SELECTPRODUCTCATEGORIES(), which is based in the database churchma_PRODUCTS_OF_MERCHANTS. This returns a list of category codes corresponding to the SIC classification. The table format is (in this order): business_type_by_sic_code, business_type_sic_code, business_type_sic_description. We then echo it in the form of '; #The size attribute here governs the visible display width of the field. We have given it a size of 100 to ensure the placeholder text is fully displayed. echo '
'; echo '
'; #We add two line breaks to ensure sufficient space between the questions. #(The answer to this goes to product_description_english. We should accept max 1000 characters (because the description should allow the merchant to give a fuller account of the item) and no fewer than 0 characters). echo ''; echo '
'; echo ''; echo '
'; echo '
'; #We add two line breaks to ensure sufficient space between the questions. #(The answer to this goes to product_price) echo ''; echo '
'; echo ''; #The step attribute means that decimal numbers to two places are accepted. Without this, only integers such as 1, 2, 3, and so on would be allowed. Currently, we do not allow merchants to offer items of £0.00 (i.e. free). One reason for this is that it could potentially bankrupt the company, in that we would be charged by Barclays ePDQ for a variety of fees even though the item is free. The min and max attributes set the minimum price and maximum price respectively - currently, the minimum price we accept is £0.01 and the maximum price we permit is £500000. This is also the same on the database, and we use the trigger called PRICEWARNINGBEFOREINSERT on the table list_of_products to prevent insertions of values that bypass this restrictions. N.B.: We should still use PHP's str_replace() function to detect if a comma has been used, and to remove this. Furthermore, we should detect if the input entered is in fact a number and not an SQL injection attempt. We can do this by using the floatval() method. echo '
'; echo '
'; #We add two line breaks to ensure sufficient space between the questions. #The answer to this goes to category_of_product_by_sic_classification. Refer to the place in this script referencing $resultOfQueryToObtainProductCategories to understand what is going on here. echo ''; echo '
'; echo ''; #This ends the select area echo '
'; echo '
'; #We add two line breaks to ensure sufficient space between the questions. #The answer to this goes into product_image_one_url. Don't forget to do check on this file. Furthermore, don't forget that on marketplace.php, we only showcase the thumbnail image in marketplace.php. Note that this is an optional field and we permit users not to upload an image. echo ''; echo ' '; echo '
'; echo '
'; #We add two line breaks to ensure sufficient space between the questions. #The answer, together with answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInterval, to this goes into estimated_time_of_arrival. Note that we only use four interval types: DAY, WEEK, MONTH, YEAR. echo ''; echo '
'; echo ''; #The answer, together with answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInteger, to this goes into estimated_time_of_arrival. Don't forget to add a "space" between the two answers. We leave the contents of this label tag blank (a la in the style of with nothing in between) because it carries on from the previous label. echo ''; echo ''; echo '
'; echo '
'; #We add two line breaks to ensure sufficient space between the questions. echo ''; echo ''; #This ends the form segment. echo 'Click or press here to be taken back to the ChurchMapped home page'; ### This segment revolves actually inserting the values into the database, which we do with PHP. Five things not to forget here: 1) use parameterised queries. 2) multiply price of item by 100 for Barclays ePDQ 3) concatenate answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInteger and answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInterval for estimated_time_of_arrival. 4) If you may forgive me for adding, but ensure that you validate that the price submitted is in fact less than or equal to 500000. 5) Be sure to take the md5 check to ensure that the same item isn't uploaded. #Values to insert in the following order, together with their data type: # merchant_id: integer (i) # product_name_english: string (s) # product_description_english: string (s) # product_price: integer (i) (this is the case because we are multiplying by 100) # category_of_product_by_sic_classification: string (s) # product_image_one_url: string (s) # estimated_time_of_arrival: string (s) (remember that we are concatenating answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInteger and answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInterval, thereby giving a string) # timestamp_of_when_product_or_service_was_added: string (s) (we generate this via PHP's date function) # md5_without_salt_check_of_product_upload: string (s) (we first need to check the md5() value in the table before using parameterised queries to insert values into the table #Note that with some columns on the database, such as api_quota_limit, this can only be added directly via root access by ChurchMapped Limited. #There are 8 values that are inserted in total. $statementToInsert = $conn->prepare("INSERT INTO churchma_PRODUCTS_OF_MERCHANTS.list_of_products(merchant_id, product_name_english, product_description_english, product_price, category_of_product_by_sic_classification, product_image_one_url, estimated_time_of_arrival, timestamp_of_when_product_or_service_was_added, md5_without_salt_check_of_product_upload) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); (int)$merchantid = $_SESSION['sessionCHURCHMAPPEDUSERID']; #This is the ID of the business - very important! if(isset($_POST['answerToQuestionOfProductOrServiceProvided']) && strlen($_POST['answerToQuestionOfProductOrServiceProvided']) > 0 && strlen($_POST['answerToQuestionOfProductOrServiceProvided']) <= 25){ #If the answer entered into the form regarding the item's name is greater than 0 as well as less than or equal to 25, we set a variable called $product_name_english $product_name_english = strip_tags($_POST['answerToQuestionOfProductOrServiceProvided']); }else{ $errors[] = "Your product name is either too long or too short. Please keep it to more than 0 characters and less than or equal to 25 characters"; } if(isset($_POST['answerToQuestionOfProductOrServiceDescription']) && strlen($_POST['answerToQuestionOfProductOrServiceDescription']) > 0 && strlen($_POST['answerToQuestionOfProductOrServiceDescription']) <= 1000){ $product_description_english = strip_tags($_POST['answerToQuestionOfProductOrServiceDescription']); }else{ $errors[] = "Your product description is either too long or too short. Please keep it to more than 0 characters and less than or equal to 1000 characters"; } if(isset($_POST['answerToQuestionForProductOrServicePrice']) && $_POST['answerToQuestionForProductOrServicePrice'] > 0 && $_POST['answerToQuestionForProductOrServicePrice'] <= 500000 && is_numeric($_POST['answerToQuestionForProductOrServicePrice'])){ #The is_numeric() function prevents the user from using a comma in the number. $product_price = $_POST['answerToQuestionForProductOrServicePrice'] * 100; #We multiply by 100 to } else{ $errors[] = "There seems to be something wrong with the price you entered. Either it is set to 0 or more than 500000 or contains an invalid character. Please revise."; } if(isset($_POST['variousproductcategories'])){ $category_of_product_by_sic_classification = $_POST['variousproductcategories']; }else{ $category_of_product_by_sic_classification = "UNCLASSIFIED"; #Because it is optional to put the item into a particular category, we do not use the errors array here but rather simply put the variable $category_of_product_by_sic_classification here and equate it to "UNCLASSIFIED". } #In this brief segment, we deal with the file upload which goes into product_image_one_url $filenameHash = "PontifexPrimacy!!!July2021"; #This is the filename salt that we use to ensure that users can't "guess" how our filenames are generated. #The $target_dir on the local Windows machine is C:\xampp\htdocs\ChurchMappedWebsite\uploads\\ . The $target_dir on the Linux production machine should be /home/churchma/uploads/ . Note one important difference: Windows uses a backward slash whilst Linux uses a forward slash. $target_dir = '/home/churchma/uploads/'; $target_file = $target_dir . basename($_FILES["answerToQuestionForProductOrServiceFilename"]["name"]); $imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION)); $target_file = $target_dir . md5(basename($_FILES["answerToQuestionForProductOrServiceFilename"]["name"]) . $filenameHash) . '.' . $imageFileType; if(isset($_POST['merchantUploadSubmit']) && getimagesize($_FILES["answerToQuestionForProductOrServiceFilename"]["tmp_name"]) !== false && $_FILES["answerToQuestionForProductOrServiceFilename"]["size"] < 500000 && ($imageFileType == "jpeg" || $imageFileType == "jpg" || $imageFileType == "png")){ #Here we check to see if the file that has been uploaded is in fact an image. We do this with the getimagesize() method - this returns false if the file is not an image, or not an image in a format we support. We use the exclamation mark and two equals sign with respect to $_FILES["answerToQuestionForProductOrServiceFilename"]["tmp_name"] because getimagesize() returns false if the file is not an image. move_uploaded_file($_FILES["answerToQuestionForProductOrServiceFilename"]['tmp_name'], $target_file); #This moves the image from the tmp area to the designated area where uploads should go. $product_image_one_url = $target_file; #We should attempt to create a thumbnail and use that. We will do that later. }else{ $errors[] = "There is something wrong with your image. Please upload it and try again. Your image must either be .jpg, .jpeg, or .png. It must also be less than 500KB"; } ## This ends the brief segment regarding the image upload. if(isset($_POST['answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInteger']) && isset($_POST['answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInterval'])){ if(is_numeric($_POST['answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInteger']) && ($_POST['answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInterval'] === "DAY" || $_POST['answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInterval'] === "WEEK" || $_POST['answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInterval'] === "MONTH" || $_POST['answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInterval'] === "YEAR")){ $estimated_time_of_arrival = $_POST['answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInteger'] . ' ' . $_POST['answerToQuestionForProductOrServiceEstimatedTimeOfArrivalAsInterval']; }else{ $errors[] = "We can't seem to understand this interval. Please revise."; } } $timestamp_of_when_product_or_service_was_added = date("Y-m-d h:i:s"); #This gives the UNIX timestamp to be inserted into the timestamp_of_when_product_or_service_was_added column. $md5_without_salt_check_of_product_upload = md5($merchantid . $product_name_english . $product_description_english. $product_price . $category_of_product_by_sic_classification . $product_image_one_url . $estimated_time_of_arrival); #We remove the timestamp from the md5 check() because otherwise it would still permit identical uploads. $statementToInsert->bind_param("ississsss", $merchantid, $product_name_english, $product_description_english, $product_price, $category_of_product_by_sic_classification, $product_image_one_url, $estimated_time_of_arrival, $timestamp_of_when_product_or_service_was_added, $md5_without_salt_check_of_product_upload); #We check to see if the item has already been uploaded into the database by checking the $md5_without_salt_check_of_product_upload $queryToCheckIfProductHasAlreadyBeenUploaded = "SELECT * FROM churchma_PRODUCTS_OF_MERCHANTS.list_of_products WHERE md5_without_salt_check_of_product_upload=" . ' " ' . $md5_without_salt_check_of_product_upload . ' " '; $resultOfQueryToCheckIfProductHasAlreadyBeenUploaded = mysqli_query($conn, $queryToCheckIfProductHasAlreadyBeenUploaded); if(empty($errors) && mysqli_num_rows($resultOfQueryToCheckIfProductHasAlreadyBeenUploaded) == 0){ #If there are no errors, we insert the values into the database. (Note as of 18th July 2021: This doesn't seem to work. It will be looked at again 30th July 2021). $statementToInsert->execute(); echo "Product successfully added!"; } elseif(mysqli_num_rows($resultOfQueryToCheckIfProductHasAlreadyBeenUploaded) > 0){ echo 'It seems you have already uploaded this item. We do not accept multiple identical product submissions. Please amend your submission or if you think this is a mistake, please contact' . 'support@churchmapped.com'; } elseif(!empty($_POST)){ #If there are indeed errors, as stored within the errors() array, we print it out. Note that we use the condition "!empty()" foreach($errors as $particularerror){ echo "
"; echo $particularerror; } } } echo ''; ?>