Forum Replies Created

Viewing 15 replies - 451 through 465 (of 574 total)
  • Plugin Author tbenyon

    (@tbenyon)

    Really appreciate you taking the time to write a review!

    Thank you 🙂

    Plugin Author tbenyon

    (@tbenyon)

    Hey @icola007,

    Glad you got it working 😊

    I know that feeling of something suddenly working and you don’t know why?!

    Would you minding writing a review for the plugin?

    I’d be SUPER grateful!

    Thanks,

    Tom

    Plugin Author tbenyon

    (@tbenyon)

    Hey @icola007,

    Reeally glad the plugin is working for you.

    I will have a look into this tongiht to see how easy this is. I initially think this should be a very straightforward feature.

    However, I’m also surprised using those other plugins are not working as I’m sticking closely to how WordPress handles login. But this is why I’ll need to look into it.

    If you haven’t heard from me tomorrow, please feel free to chase me as I hope to at least update you by then.

    Tom 🙂

    Plugin Author tbenyon

    (@tbenyon)

    Hey @rjjacob,

    I think the thing that feels initially strange about this flow is that WordPress uses the Username as the unique identifier. For this reason I am doing the same.

    By setting the username to the e-mail and then wanting this to be able to be updated, as a unique identifier, doesn’t make sense.

    I welcome your opinion on the way this would be solved but my current thought is:

    • You set the username in WordPress to be the primary key of the external Users table (assuming this is what that system uses to uniquely identify users. This would need testing as I don’t know how grumpy WordPress will get if your primary key is an integer.
    • This would still require that external login would support e-mail as a (as well as username) when logging in.

    Again your opinion is welcomed.

    Tom

    Plugin Author tbenyon

    (@tbenyon)

    I’m presuming this is resolved as I haven’t heard back so I’m going to mark this as so.

    If this is not the case, please feel free to get back to me and we’ll continue to look into this for you.

    Thanks,

    Tom 🙂

    Plugin Author tbenyon

    (@tbenyon)

    Hey @xprojectsx,

    Sincere apologies for the delay.

    New thing to try.

    1) Replace all the code in options/wpconfig_options.php with the below:

    
    <?php
    
    //If a constant is defined for an option, make that take president
    function exlog_get_option($option_name) {
        $constant_option_name = strtoupper($option_name);
    
        if (defined($constant_option_name)) {
            return constant($constant_option_name);
        } else {
            $option_data = get_option($option_name);
            $decoded_option_data = json_decode(base64_decode($option_data), true);
            if ($decoded_option_data) {
                return exlog_modify_repeater_data_for_view_use($decoded_option_data); // If data was JSON return PHP array (for repeater fields)
            }
            return $option_data;
        }
    }
    
    function exlog_is_wpconfig_option_set($option_name) {
        return defined(strtoupper($option_name));
    }
    
    function exlog_modify_repeater_data_for_view_use($repeater_data) {
        $formatted_data = array();
        if (is_array($repeater_data)) {
            foreach ($repeater_data as $repeater_item_data) {
                $formatted_repeater_items_data = array();
                foreach ($repeater_item_data as $repeater_item_datum) {
                    if ($repeater_item_datum['repeater_field']) {
                        $value = exlog_modify_repeater_data_for_view_use($repeater_item_datum['value']);
                    } else {
                        $value = $repeater_item_datum['value'];
                    }
                    $formatted_repeater_items_data[$repeater_item_datum['name']] = $value;
                }
                array_push($formatted_data, $formatted_repeater_items_data);
            }
            return $formatted_data;
        } else {
            return $repeater_data;
        }
    }
    

    2) Replace all the code in js/exlog_repeater_field_handler.js with this:

    
    // Possible data structure
    
    var possible_repeater_data_master = [
      {
        "field_name": "title",
        "id": "12345678",
        "repeater_field": false,
        "field_data": "this is some text"
      },
      {
        "field_name": "Names Repeater",
        "id": "12345678",
        "repeater_field": true,
        "field_data": [ // Repeater items
          [ // Repeater Item
            {
              "field_name": "title in repeater",
              "id": "12345678",
              "repeater_field": false,
              "field_data": "this is some textoooo"
            },
            {
              "field_name": "title in repeater222",
              "id": "12345678",
              "repeater_field": false,
              "field_data": "this is some textoooo222"
            },
            {
              "field_name": "inner_repeater",
              "id": "12345678",
              "repeater_field": true,
              "field_data": [ // Repeater Items
                [ // Repater Item
                  {
                    "field_name": "title in repeater333",
                    "id": "12345678",
                    "repeater_field": false,
                    "field_data": "this is some textoooo3333"
                  },
                  {
                    "field_name": "title in repeater3331",
                    "id": "12345678",
                    "repeater_field": false,
                    "field_data": "this is some textoooo3332"
                  }
                ],
                [ // Repater Item 2
                  {
                    "field_name": "title in repeater333",
                    "id": "12345678",
                    "repeater_field": false,
                    "field_data": "this is some textoooo3333"
                  },
                  {
                    "field_name": "title in repeater3331",
                    "id": "12345678",
                    "repeater_field": false,
                    "field_data": "this is some textoooo3332"
                  }
                ]
              ]
            }
          ]
        ]
      }
    ];
    
    (function ($) {
      $(function () {
        var $parent_repeater_fields = $(".option-container.repeater.exlog-repeater-master");
        var repeater_buttons_selector = ".exlog_repeater_add_button";
        var repeater_item_selector = ".repeater_item";
        var repeater_data_attr = 'data-exlog-repeater-id';
        var master_markup_item_selector = repeater_item_selector + '[' + repeater_data_attr + '="0"]';
        var click_event_name = 'click';
        var change_events = ['keyup', 'paste', 'change'];
        var repeater_data_store_selector = '.exlog_repeater_data_store';
    
        function object_to_string_if_object(data) {
          if (typeof data === 'object') {
            try {
              data = JSON.stringify(data);
            } catch (error) {
              //  Leave data in current format
            }
          }
          return data
        }
    
        function place_data_from_db() {
          function place_specific_repeater_values($parent_element, data) {
            try {
              data = JSON.parse(data);
            } catch(e) {
              //
            }
    
          //  Put all data in the name value
            var $data_store = $parent_element.children(repeater_data_store_selector);
            var data_string;
            try {
              data_string = JSON.stringify(data);
            } catch(e) {
              console.log('EXLOG: Failed to parse some data from the database.\nIncorrect data:', data);
              data_string = false;
            }
    
            $data_store.val(btoa(data_string));
    
            // Of that data, put each value in the correct input
            if (data && data != 'false') {
              data.forEach(function (repeater_item, i) {
                var $repeater_item;
                if (i > 0) { // If not the first repeater item - create a new one in the DOM and store the jQuery object
                  $repeater_item = create_new_item($parent_element, i, false);
                } else { // If the first repeater item, get the one repeater jQuery object
                  $repeater_item = $parent_element.find('> .repeater_item');
                }
    
                repeater_item.forEach(function (repeater_item_option) {
    
                  var input_data = object_to_string_if_object(repeater_item_option['value']);
    
                  var input_selector = '> .repeater_item_input_container > .option-container > [name="' + repeater_item_option['name'] + '"]';
    
                  var $input_item = $repeater_item.find(input_selector);
    
                  // Add DB value into the input
                  if ($input_item.attr('type') === 'checkbox') {
                    $input_item.prop('checked', input_data);
                  } else {
                    $input_item.val(input_data);
                  }
    
                  //  If the input data is a repeater field, call this function again with the relevant data and $parent element
                  if (repeater_item_option['repeater_field']) {
                    var $next_repeater = $input_item.closest('.option-container');
                    place_specific_repeater_values($next_repeater, repeater_item_option['value']);
                  }
                });
              });
            }
          }
    
          // Put the master data into the repeater master component and then start placing specific values
          $parent_repeater_fields.each(function () {
            var $parent_repeater_field = $(this);
            var base_64_string = $parent_repeater_field.children(repeater_data_store_selector).val();
            var parent_repeater_data;
            try {
              parent_repeater_data = JSON.parse(atob(atob(base_64_string))); // Once for storing from server, once for data store
            } catch (e) {
              parent_repeater_data = false;
            }
            place_specific_repeater_values($parent_repeater_field, parent_repeater_data)
          });
    
        }
    
        function create_new_item($repeater_option_container, item_id) {
          // Get the markup to copy from looking at first item
          var markup = $repeater_option_container.children(master_markup_item_selector).html();
    
          var $markup = $('<section class="repeater_item">' + markup + '</section>');
    
          // Clean out any repeater ids that are not the first one from the generated markup
          $(repeater_item_selector, $markup).each(function () {
            var $repeater_item = $(this);
            if ($repeater_item.attr(repeater_data_attr) !== "0") {
              $repeater_item.remove();
            }
          });
    
          // Store the new id in the attr of the repeater item
          $markup.attr('data-exlog-repeater-id', item_id);
    
          var $markup_inputs = $markup.children('.repeater_item_input_container').find('.option-container input:not([type=button])');
    
          $markup_inputs.each(function () {
            var $markup_input = $(this);
            $markup_input.val('');
          });
    
          // Place the new markup on the page
          $repeater_option_container.children('.add_more').before($markup);
          reset_watchers();
          return $markup;
        }
    
        function reselect_add_buttons() {
          $(repeater_buttons_selector).off(click_event_name).on(click_event_name, on_add_button_click);
        }
    
        function on_add_button_click() {
          var $button = $(this);
    
          var $add_more_container = $button.closest(".add_more");
    
          var used_ids = [];
          // Store all used ids
          $add_more_container.siblings(repeater_item_selector).each(function () {
            var $repeater_item = $(this);
            var repeater_item_id = $repeater_item.attr(repeater_data_attr);
            used_ids.push(repeater_item_id)
          });
    
          // Get a unique id for the new repeater item
          var new_id = 1;
          while (used_ids.indexOf(new_id.toString()) > -1 ) { // While new_id is in used_ids
            new_id += 1;
          }
    
          var $repeater_option_container = $add_more_container.closest('.option-container.repeater');
    
          create_new_item($repeater_option_container, new_id, false);
        }
        
        function monitorRepeaterInputs() {
          var change_events_string = change_events.join(' ');
          var $repeater_data_stores = $(repeater_data_store_selector);
    
          // For every repeater data store
          $repeater_data_stores.each(function () {
            var $repeater_data_store = $(this);
    
            // Get just the inputs for that data store
            var $inputs = $repeater_data_store.siblings('.repeater_item').children('.repeater_item_input_container').children('.option-container').children('input, textarea');
    
            // Clear previous change events on inputs
            $inputs.off(change_events_string);
    
            // For each input, if there is a change in the data, update the data in the repeater data store
            $inputs.on(change_events_string, (function () {
              update_repeater_data($repeater_data_store, $inputs);
    
              // After the data store data is updated, trigger an event change on the store so that parent stores will update their data
              $repeater_data_store.trigger(change_events[0]);
            }));
          });
        }
    
        function update_repeater_data($repeater_data_store) {
          var data_for_store = []; // Object to be populated by first child inputs of repeater
          var repeater_items = $repeater_data_store.siblings('.repeater_item');
          repeater_items.each(function () {
            var repeater_item = [];
            var $inputs = $(this).children('.repeater_item_input_container').children('.option-container').children('input, textarea');
            $inputs.each(function () {
              var $input = $(this);
              var value;
    
              if ($input.attr('type') === 'text') {
                value = $input.val();
              } else if ($input.attr('type') === 'checkbox') {
                value = $input.prop('checked');
              } else {
                value = null;
              }
    
              var repeater_field = false;
              if ($input.hasClass('exlog_repeater_data_store')) {
                repeater_field = true;
                try {
                  value = JSON.parse(atob(value)); // If the data can be interpreted as JSON convert it to an object
                } catch (error) {
                  console.log("EXLOG: Error storing repeater data for '" + $input.attr('name') + "'", "Value:", value);
                }
              }
              repeater_item.push({
                "name": $input.attr('name'),
                "repeater_field": repeater_field,
                "value": value
              });
            });
            data_for_store.push(repeater_item);
          });
    
          $repeater_data_store.val(btoa(JSON.stringify(data_for_store)));
        }
    
        function on_delete_item_click() {
          $('.delete_repeater_item').off(click_event_name).on(click_event_name, function () {
            var $delete_button = $(this);
            var $repeater_item = $delete_button.closest(repeater_item_selector);
    
            // var $repeater_item_name = $repeater_item.attr('name');
    
            var $first_item = $repeater_item.siblings(master_markup_item_selector).find('input');
    
            // Remove the repeater item from the DOM
            $repeater_item.remove();
    
            // Ensure elements no longer in the DOM are no longer selected as otherwise data is kept
            reset_watchers();
    
            // Make first repeater item trigger change to remove deleted items data
            $first_item.trigger(change_events[0]);
          })
        }
    
        // All the selectors that need updating on DOM change
        function reset_watchers() {
          monitorRepeaterInputs();
          reselect_add_buttons();
          on_delete_item_click();
        }
    
        place_data_from_db();
        reset_watchers();
      })
    }(jQuery));
    

    Let me know if this works.

    Tom

    Plugin Author tbenyon

    (@tbenyon)

    Hey @xprojectsx,

    I really appreciate you going to all this effort. I think it seems to be working for other people.

    Your efforts trying is enough of a reward for me 🙂

    Thank you very much! 🙂 🙂 🙂

    I’ll mark this as resolved for now.

    Thanks,

    Tom

    Plugin Author tbenyon

    (@tbenyon)

    Sorry for the delay.

    So are you telling me that on a local copy the test button works AND you can login with the external database?

    However, on your live website, the test button works but the password is wrong?

    Have you definitely got the same password hashing settings on your live environment?

    If what I have said above is correct, and you’ve double checked the hashing settings then could you please send me your php logs that happen when you click the login button.

    Thanks,

    Tom

    Plugin Author tbenyon

    (@tbenyon)

    Hey @piska,

    Haven’t heard from you for a while. Just seeing how you’re getting on?

    Thanks,

    Tom

    Plugin Author tbenyon

    (@tbenyon)

    Hi @rohail1122,

    We are not allowed to share personal contact details with each other in this forum. It breaks WordPress’ code of conduct.

    I imagine the issue you are having is with your connection to the database on the live service.

    Does the “Test” button work?

    If not, can you tell me the error you received on screen.

    Thanks,

    Tom

    Forum: Plugins
    In reply to: [External Login] Tables
    Plugin Author tbenyon

    (@tbenyon)

    Hey Americo,

    I’m sorry to say that this functionality isn’t currently supported.

    I can add this in the future but I have a backlog of feature requests. With my main job and private work this isn’t likely to get done any time soon. This is a hobby for me.

    My recommendation would be to get someone to do this integration for you as private work as it seems like a relatively unique issue.

    Although some people have found me on Facebook and Linked In, I am not allowed to give out my details as it breaks the WordPress code of practice. For this reason I’d recommend looking for a developer who would be willing to take on this work for you.

    Sorry I can’t be of more help but I will add this to the backlog as I may get round to it one day.

    I’ll mark it as resolved on the basis that the answer is “it is not supported” and it is on the backlog for a potential future feature.

    If you have any more questions however get back to me as I’d be happy to help.

    Sorry I can’t be more helpful.

    Thanks,

    Tom

    Plugin Author tbenyon

    (@tbenyon)

    And just to double check, with the new JS (with the base 64 conversion) does this work without setting the input type to text?

    I’d be surprised if once you’ve updated the field on the front end then hit save that it doesn’t get stored in the database.

    Plugin Author tbenyon

    (@tbenyon)

    I expect that on page load, but when you have changed type to be text, and then typed in some boxes, do you see a base 64 encoded value, and does it save to the database when you click save?

    Plugin Author tbenyon

    (@tbenyon)

    To clarify:

    1. You updated the JS with the code I gave you above
    2. You modified a field
    3. In there find the INPUT with the class “exlog_repeater_data_store” you can see a base 64 encoded string (a jumble of characters that ends with an equal sign = )
    4. When you click save the only thing you see in the database is “false”

    When we did this by manually modifying the “FUNKY MONKEY” string previously the data did get saved in the database. This is so weird to me. I don’t know why Funky Monkey would get saved but the base64 string wouldn’t.

    The only other differences I can think of is that:

    • The input field was not hidden
    • We manually typed it into the field

    Let’s see which one of these makes a differences.

    Firstly let’s just try making the hidden input non hidden.
    In “wp-content/plugins/external-login/views/form_elements/repeater_field.php”

    Let’s modify the below code so it no longer says hidden as the type like this:

        <input
          style="background-color: red;"
          class="exlog_repeater_data_store"
          type="hidden"
          name="<?php echo $form_field["field_slug"]; ?>"
          <?php if (!$exlog_parent_repeater_slug) : ?>
            value="<?php echo base64_encode(get_option($form_field["field_slug"])); ?>"
          <?php endif; ?>
        >

    . . . and change it to say text like this:

        <input
          style="background-color: red;"
          class="exlog_repeater_data_store"
          type="text"
          name="<?php echo $form_field["field_slug"]; ?>"
          <?php if (!$exlog_parent_repeater_slug) : ?>
            value="<?php echo base64_encode(get_option($form_field["field_slug"])); ?>"
          <?php endif; ?>
        >

    After this let’s go through the process of:

    1. Reloading the page
    2. Typing something in the input fields
    3. make sure we can see the base 64 data appearing in that red field
    4. Hit save
    5. Check if anything saved to the database (Don’t forget to refresh 😛 )

    Thanks,

    Tom

    Plugin Author tbenyon

    (@tbenyon)

    Brilliant news! I think we’re close to solving this one!

    Still confused why it works in mine and not in yours.

    Let’s try base64 encoding all data going to the database.

    Could you replace everything in ‘wp-content/plugins/external-login/js/exlog_repeater_field_handler.js’ with the following code:

    
    
    (function ($) {
      $(function () {
        var $parent_repeater_fields = $(".option-container.repeater.exlog-repeater-master");
        var repeater_buttons_selector = ".exlog_repeater_add_button";
        var repeater_item_selector = ".repeater_item";
        var repeater_data_attr = 'data-exlog-repeater-id';
        var master_markup_item_selector = repeater_item_selector + '[' + repeater_data_attr + '="0"]';
        var click_event_name = 'click';
        var change_events = ['keyup', 'paste'];
        var repeater_data_store_selector = '.exlog_repeater_data_store';
    
        function object_to_string_if_object(data) {
          if (typeof data === 'object') {
            try {
              data = JSON.stringify(data);
            } catch (error) {
              //  Leave data in current format
            }
          }
          return data
        }
    
        function place_data_from_db() {
          function place_specific_repeater_values($parent_element, data) {
          //  Put all data in the name value
            var $data_store = $parent_element.children(repeater_data_store_selector);
            var data_string;
            try {
              data_string = JSON.stringify(data);
            } catch(e) {
              console.log('EXLOG: Failed to parse some data from the database.\nIncorrect data:', data);
              data_string = false;
            }
            $data_store.val(data_string);
    
            // Of that data, put each value in the correct input
            if (data) {
              data.forEach(function (repeater_item, i) {
                var $repeater_item;
                if (i > 0) { // If not the first repeater item - create a new one in the DOM and store the jQuery object
                  $repeater_item = create_new_item($parent_element, i, false);
                } else { // If the first repeater item, get the one repeater jQuery object
                  $repeater_item = $parent_element.find('> .repeater_item');
                }
    
                repeater_item.forEach(function (repeater_item_option) {
    
                  var input_data = object_to_string_if_object(repeater_item_option['value']);
    
                  var input_selector = '> .repeater_item_input_container > .option-container > [name="' + repeater_item_option['name'] + '"]';
    
                  // Add DB value into the input
                  var $input_element = $repeater_item.find(input_selector).val(input_data);
    
                  //  If the input data is a repeater field, call this function again with the relevant data and $parent element
                  if (repeater_item_option['repeater_field']) {
                    place_specific_repeater_values($input_element.closest('.option-container'), repeater_item_option['value']);
                  }
                });
              });
            }
          }
    
          // Put the master data into the repeater master component and then start placing specific values
          $parent_repeater_fields.each(function () {
            var $parent_repeater_field = $(this);
            var base_64_string = $parent_repeater_field.children(repeater_data_store_selector).val();
            var parent_repeater_data;
            try {
              parent_repeater_data = JSON.parse(atob(atob(base_64_string))); // Once for storing from server, once for data store
            } catch (e) {
              parent_repeater_data = false;
            }
            place_specific_repeater_values($parent_repeater_field, parent_repeater_data)
          });
    
        }
    
        function create_new_item($repeater_option_container, item_id) {
          // Get the markup to copy from looking at first item
          var markup = $repeater_option_container.children(master_markup_item_selector).html();
    
          var $markup = $('<section class="repeater_item">' + markup + '</section>');
    
          // Clean out any repeater ids that are not the first one from the generated markup
          $(repeater_item_selector, $markup).each(function () {
            var $repeater_item = $(this);
            if ($repeater_item.attr(repeater_data_attr) !== "0") {
              $repeater_item.remove();
            }
          });
    
          // Store the new id in the attr of the repeater item
          $markup.attr('data-exlog-repeater-id', item_id);
    
          var $markup_inputs = $markup.children('.repeater_item_input_container').find('.option-container input:not([type=button])');
    
          $markup_inputs.each(function () {
            var $markup_input = $(this);
            $markup_input.val('');
          });
    
          // Place the new markup on the page
          $repeater_option_container.children('.add_more').before($markup);
          reset_watchers();
          return $markup;
        }
    
        function reselect_add_buttons() {
          $(repeater_buttons_selector).off(click_event_name).on(click_event_name, on_add_button_click);
        }
    
        function on_add_button_click() {
          var $button = $(this);
    
          var $add_more_container = $button.closest(".add_more");
    
          var used_ids = [];
          // Store all used ids
          $add_more_container.siblings(repeater_item_selector).each(function () {
            var $repeater_item = $(this);
            var repeater_item_id = $repeater_item.attr(repeater_data_attr);
            used_ids.push(repeater_item_id)
          });
    
          // Get a unique id for the new repeater item
          var new_id = 1;
          while (used_ids.indexOf(new_id.toString()) > -1 ) { // While new_id is in used_ids
            new_id += 1;
          }
    
          var $repeater_option_container = $add_more_container.closest('.option-container.repeater');
    
          create_new_item($repeater_option_container, new_id, false);
        }
        
        function monitorRepeaterInputs() {
          var change_events_string = change_events.join(' ');
          var $repeater_data_stores = $(repeater_data_store_selector);
    
          $repeater_data_stores.each(function () {
            var $repeater_data_store = $(this);
            var $inputs = $repeater_data_store.siblings('.repeater_item').children('.repeater_item_input_container').children('.option-container').children('input, textarea');
    
            // Clear previous events
            $inputs.off(change_events_string);
    
            $inputs.on(change_events_string, (function () {
              update_repeater_data($repeater_data_store, $inputs);
              $repeater_data_store.trigger(change_events[0]);
            }));
          });
        }
    
        function update_repeater_data($repeater_data_store) {
          var data_for_store = []; // Object to be populated by first child inputs of repeater
          var repeater_items = $repeater_data_store.siblings('.repeater_item');
          repeater_items.each(function () {
            var repeater_item = [];
            var $inputs = $(this).children('.repeater_item_input_container').children('.option-container').children('input, textarea');
            $inputs.each(function () {
              var $input = $(this);
              var value = $input.val();
              var repeater_field = false;
              if ($input.hasClass('exlog_repeater_data_store')) {
                repeater_field = true;
                try {
                  value = JSON.parse(atob(value)); // If the data can be interpreted as JSON convert it to an object
                } catch (error) {
                  console.log("EXLOG: Error storing repeater data for '" + $input.attr('name') + "'", "Value:", value);
                }
              }
              repeater_item.push({
                "name": $input.attr('name'),
                "repeater_field": repeater_field,
                "value": value
              });
            });
            data_for_store.push(repeater_item);
          });
    
          $repeater_data_store.val(btoa(JSON.stringify(data_for_store)));
        }
    
        function on_delete_item_click() {
          $('.delete_repeater_item').off(click_event_name).on(click_event_name, function () {
            var $delete_button = $(this);
            var $repeater_item = $delete_button.closest(repeater_item_selector);
    
            // var $repeater_item_name = $repeater_item.attr('name');
    
            var $first_item = $repeater_item.siblings(master_markup_item_selector).find('input');
    
            // Remove the repeater item from the DOM
            $repeater_item.remove();
    
            // Ensure elements no longer in the DOM are no longer selected as otherwise data is kept
            reset_watchers();
    
            // Make first repeater item trigger change to remove deleted items data
            $first_item.trigger(change_events[0]);
          })
        }
    
        // All the selectors that need updating on DOM change
        function reset_watchers() {
          monitorRepeaterInputs();
          reselect_add_buttons();
          on_delete_item_click();
        }
    
        place_data_from_db();
        reset_watchers();
      })
    }(jQuery));
    
Viewing 15 replies - 451 through 465 (of 574 total)