* transliterate when generating attribute names to avoid emptiness If you enter a non-ascii (non `\w` matching) string as the profile field label, the attribute name is the empty string. This causes problems outlined in #16391 To avoid losing user provided data, transliterate (Sterile is the same tool we're using for Article#title_to_slug) before matching against the word regex. If we persist an empty string, or persist a non-`\w` name, the coordinating regex in Profile::ATTRIBUTE_NAME_REGEX will leave a nil match and raise NoMethodError (the method missing is for match[:attribute_name] when match was nil, not Profile - that's the next commit. * Guard against nil matches If an attribute name doesn't match the regex, the match is nil, and trying to access (nil)[:attribute_name] raises a NoMethodError. If there was no match, assume profile does not respond to the selector, and don't handle it in method missing. * Ensure generated attribute name is valid before saving This raises a validation error if the generated name (from the label) would be empty. It's not an optimal error message (since the user can't see the internal attribute name) but it prevents persisting broken/empty data * actually raise error when validating validate/valid? only return true or false (and set errors on the object). In order to reject the creation, we need to raise a validation error, not only call validate. I think this is because the execution of before_create hooks happens after validation (which is why the validation was only checked on update, not create). * Generate an attribute name completely independent of the field label This prevents mistakenly labeling a field "Class" or "Association" or one of the other hundred public methods an AR model like Profile exposes. Since attribute_name will be passed to `profile.public_send` we really shouldn't build selectors from user supplied inputs. * Use the admin supplied label in the sidebar The profile decorator is used in the Users#show page to populate the sidebar fields. Don't use the attribute name (which we mangled during creation, and now generate randomly) as the label, use the label. * Make the label lookup null safe, and filter attributes more * Update data update script to not expect predictable labels This is low impact since it ran in july, but we no longer know what attribute name a label will create. * Fix moderator spec "Test Field" label no longer predictably generates :test_field as an attribute name. Ask the field what it's name is before asking profile about it. * Fix profile preview card request spec Remove the assumption that profile responds to a method name based on the label for Work and Education fields. * Update old DUS and its test This can probably be archived * Update profile spec to use fields generated attribute names We used to "know" how attributes were generated from labels. Now we don't. * update e2e seeds for profile field change * Don't expect attribute name to be based on the label * expect created profile fields respond to their attribute name * Update system test The label, not the attribute name, is shown on the profile form (the field has an id related to the attribute name, but the view shows the mutable/human-readable label). * Keep the field title lowercase when sending the json preview card The userMetadata component expects "work" and "education" to be attributes of the metadata, but the ui_attributes_for() method was titlizing these (for display). Ideally we wouldn't have "special purposed" these two field names, but they're there. * update profile field by attribute name, not based on label * Update profile field removal assumptions We don't know what the method selectors will be, we have to ask. * remove old test * Update translations for Education and Work Since the ui_attributes_for(area:) now gives the label, not the attribute, we need to match the label of the profile field. Note to self: this exposes an issue in localizing the custom profile fields (probably a bigger problem for large, international communities like DEV than some others, but trying to match static translation files against user-modifiable database records seems like a problem we'll see again). * Empty commit to retrigger buildkite * Remove profile field migration update scripts Cloned the specs from the other "remove unused scripts" script. * Remove unused scripts The data update script removes the entry from the table (recording that these have run) - we also want to remove the files (preventing them from running again). * remove unneeded spec for removed file * when translation for header area field not found, use the title Only Work and Education already have keys in the yml translation file, and there's not a great (or easy?) way to make multiple translations on these fields right now. Since an admin can create a new field, and assign it to the header area, we can't assume the code has a configured translation key for this field. Fallback to the title (we do this in another context already) if there's no translation. * PR feedback: Avoid n+1 query for labels the original implementation of "label_for_attribute" had an n+1 query looping over each matched key. Follow suggested improvement and pull labels and attributes at once from the db and modify the returned hash. * Downcase title before looking for translation key This avoids putting "odd" capitalized keys into the yml translation file Revert addition of "Work" and "Education" to the users files. * use a let binding for duplicated test data * Update app/models/profile_field.rb prefer SecureRandom.hex for a dashless uuid (instead of removing the dashes). Co-authored-by: Jamie Gaskins <jgaskins@hey.com> Co-authored-by: Jamie Gaskins <jgaskins@hey.com>
75 lines
2.6 KiB
Text
75 lines
2.6 KiB
Text
<div class="-mt-4">
|
|
<a href="<%= actor.path %>" class="flex">
|
|
<span class="<% if actor.class.name == "User" %>crayons-avatar crayons-avatar--xl<% elsif actor.class.name == "Organization" %>crayons-logo crayons-logo--xl<% end %> mr-2 shrink-0">
|
|
<img src="<%= actor.profile_image_url_for(length: 90) %>" class="<% if actor.class.name == "User" %>crayons-avatar__image<% elsif actor.class.name == "Organization" %>crayons-logo__image<% end %>" alt="" loading="lazy" />
|
|
</span>
|
|
<span class="crayons-link crayons-subtitle-2 mt-5"><%= actor.name %></span>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="print-hidden">
|
|
<%= follow_button(actor, style = "", classes = "w-100") %>
|
|
</div>
|
|
<% if actor.tag_line.present? %>
|
|
<div class="color-base-70">
|
|
<%= truncate (actor.tag_line || actor.summary || t("views.sticky.no_summary")), length: 200 %>
|
|
</div>
|
|
<% end %>
|
|
|
|
<% if actor.class.name == "User" %>
|
|
<div class="user-metadata-details">
|
|
<ul class="user-metadata-details-inner">
|
|
<% if actor.setting.display_email_on_profile && context != "sidebar" %>
|
|
<li>
|
|
<div class="key">
|
|
<%= t("views.sticky.email") %>
|
|
</div>
|
|
<div class="value">
|
|
<a href="mailto:<%= actor.email %>"><%= actor.email %></a>
|
|
</div>
|
|
</li>
|
|
<% end %>
|
|
<% if actor.profile.location.present? %>
|
|
<li>
|
|
<div class="key">
|
|
<%= t("views.sticky.location") %>
|
|
</div>
|
|
<div class="value">
|
|
<%= actor.profile.location %>
|
|
</div>
|
|
</li>
|
|
<% end %>
|
|
<% if (header_fields = actor.profile.decorate.ui_attributes_for(area: :header)).present? %>
|
|
<% header_fields.sort.each do |title, value| %>
|
|
<li>
|
|
<div class="key">
|
|
<%= t("views.users.profile_fields.#{title.downcase}", default: title.titleize) %>
|
|
</div>
|
|
<div class="value">
|
|
<%= value %>
|
|
</div>
|
|
</li>
|
|
<% end %>
|
|
<% end %>
|
|
<li>
|
|
<div class="key">
|
|
<%= t("views.sticky.joined") %>
|
|
</div>
|
|
<div class="value">
|
|
<%= local_date(actor.created_at) %>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<% elsif actor.class.name == "Organization" && actor.approved_and_filled_out_cta? %>
|
|
<div>
|
|
<%= sanitize_rendered_markdown(actor.cta_processed_html) %>
|
|
</div>
|
|
<% if actor.cta_button_text.present? && actor.cta_button_url.present? %>
|
|
<div>
|
|
<a href="<%= actor.cta_button_url %>" class="crayons-btn crayons-btn--outlined w-100">
|
|
<%= actor.cta_button_text || t("views.sticky.default_cta") %>
|
|
</a>
|
|
</div>
|
|
<% end %>
|
|
<% end %>
|