| Module | ContentUrlMapping |
| In: |
app/controllers/content_url_mapping.rb
|
Mixin module: URL content mapping component, of the content controller.
Note that the web server must be restarted to recognize changes within this file.
URL content mapping.
Isolate the project page path, which contains the content type name, from a URL content reference.
# File app/controllers/content_url_mapping.rb, line 13
13: def url_extract_content_type_name (
14: path_info
15: )
16: path_info.split(/\//)[1]
17: end
URL content mapping.
List all fields that are permitted for use by URL content mapping, for the current content table.
Certain database fields are excluded from front end URL content mapping for security reasons, hence a field is available via the URL only if:
Hidden fields, as specified in index_hidden_fields, are available via the URL if requested, however they are not listed in content indices.
# File app/controllers/content_url_mapping.rb, line 38
38: def url_get_available_fields (
39: content_type_id,
40: excluded_fields
41: )
42: # List of fields that are available for this URL content mapping.
43: fields = []
44: table_name = ContentItem.set_table(content_type_id)
45:
46: # The first time that ActiveRecord's columns() method is called it examines
47: # the table it is processing and sets the instance variable @columns,
48: # all subsequent calls to the columns() method return @columns without
49: # examining the underlying table, even if it is changed. For this reason
50: # the columns of the current table must be determined using the connection
51: # object.
52: columns = ContentItem.connection.columns(table_name,
53: table_name + " Columns")
54:
55: # Process all fields for this content table, including only fields
56: # that possess values in the database.
57: for column in columns.map
58: field = column.name
59: count = ContentItem.count_by_sql('SELECT count(*) ' +
60: 'FROM ' + table_name + "
61: WHERE #{field} IS NOT NULL
62: AND #{field} != ''
63: AND active = 'TRUE'")
64: fields << field if (!excluded_fields.has(field) && (count > 0))
65: end
66:
67: fields
68: end
URL content mapping.
Place all content items that are scheduled for a content index into the two dimensional array @index[‘map’], where each second level array is of the format:
[<item data>, <path>, <item id>, <title>, <append id flag>]
The <title> and <append id flag> elements are optional.
The <append id flag> instructs the calling method to append the current content item‘s database row id to the end of the URL, in order to ensure the uniqueness of each result.
# File app/controllers/content_url_mapping.rb, line 85
85: def url_get_index_map (
86: project_id,
87: project_host_id
88: )
89: # The base path used by href targets on this page.
90: path = @index['path_info']
91:
92:
93: # Interface to has(), particular to the processing of @index['map'].
94: #
95: # This proc truncates duplicate elements from @index['map']:
96: # * @index['map'] is a two dimensional array that must be searched
97: # using only the first element of each sub-array; the remaining elements
98: # of each sub-array must be discarded, in order to avoid false positives
99: # that could result from the flatten()'ing of @index['map'] via has()
100: # * has() flattens it's input array, and searches the resulting one
101: # dimensional array for a pattern
102: e = proc {
103: |value|
104: if (@index['map'].size > 0)
105: return @index['map'].transpose[0].has(value)
106: else
107: return FALSE
108: end
109: }
110:
111:
112: # Index by the name of a content type.
113: if (URL_CONTENT_TYPE_NAME == @index['reference_type'])
114: path = '/' + @index['type'].name
115:
116: # Note that @index['map'] is populated immediately within this
117: # conditional, whereas all other conditionals in this block utilize a
118: # proc looping over @type.items to populate @index['map'].
119: for field in @index['type'].index_fields
120: @index['map'] << [field, path, 1] if (!e.call(field))
121: end
122:
123: # Index by database field, resulting in display of all possible values
124: # for this field.
125: elsif (URL_FIELD == @index['reference_type'])
126: # Insert only non-empty values.
127: p = proc {
128: |item, path|
129: field = @index['field']
130: if (item && item["#{field}""#{field}"])
131: @index['map'] << [item["#{field}""#{field}"], path, item.id]
132: end
133: }
134:
135: # Index by database field, resulting in display of all possible values
136: # for this field and showing all duplicate values.
137: elsif (@index['all'])
138: path = '/' + @index['type'].name
139:
140: # Insert only non-empty values.
141: p = proc {
142: |item, path|
143: field = @index['field']
144: if (item && item["#{field}""#{field}"])
145: # Convert date values (ActiveRecord datetime timestamps) to the URL
146: # safe search string format used by Code Blue, and remove the time
147: # zone from the title.
148: if ('date' == field)
149: @index['map'] << [item["#{field}""#{field}"],
150: path,
151: item.id,
152: item["#{field}""#{field}"].to_s.gsub(/ -\d{4}/, '')]
153: else
154: @index['map'] << [item["#{field}""#{field}"],
155: path,
156: item.id]
157: end
158: end
159: }
160:
161: # Index by date or date/title.
162: elsif (URL_DATE == @index['reference_type'])
163: # Index for a specific day, displaying the titles of all available content
164: # items for this day.
165: #
166: # Note that URL content mapping for URL_DATE_TITLE is handled by this
167: # conditional.
168: if (@index['day'])
169: p = proc {
170: |item, path|
171: if (item.title)
172: @index['map'] << [item.title, path, item.id]
173: end
174: }
175:
176: # Index by month, displaying all days for which there are
177: # content items of this content type, for the month being processed.
178: elsif (@index['month'])
179: p = proc {
180: |item, path|
181: if (item && item.date)
182: i_day = item.date.day
183: @index['map'] << [i_day, path, item.id] if !(e.call(i_day))
184: end
185: }
186:
187: # Index by year, displaying all months for which there are
188: # content items of this content type, for the year being processed.
189: elsif (@index['year'])
190: p = proc {
191: |item, path|
192: if (item && item.date)
193: i_month = item.date.month
194:
195: if !(e.call(i_month))
196: @index['map'] << [i_month,path, item.id, item.date.strftime('%B')]
197: end
198: end
199: }
200:
201: # Index by content type date, displaying all years for which there are
202: # content items of this content type.
203: else
204: p = proc {
205: |item, path|
206: if (item && item.date)
207: i_year = item.date.year
208: @index['map'] << [i_year, path, item.id] if !(e.call(i_year))
209: end
210: }
211:
212: end
213:
214: # Index by title or date. Display all possible titles.
215: # URL_TITLE, URL_FIELD_VALUE.
216: else
217: p = proc {
218: |item, discard|
219:
220: if (@index['type'].index_title_field)
221: field = @index['type'].index_title_field
222: else
223: field = @index['field']
224: end
225:
226: path = "/#{@index['type'].name}/#{field}"
227:
228: if (item && item["#{field}""#{field}"])
229: @index['map'] << [item["#{field}""#{field}"], path, item.id]
230: end
231: }
232:
233: end
234:
235:
236: # Populate the index map array, @index['map'], using the appropriate proc
237: # as defined above.
238: if (p)
239: for id in @index['type'].items.keys
240: p.call(item(project_id, project_host_id, @index['type'].id, id), path)
241: end
242: end
243:
244:
245: # Convert SCSV into individual @index['map'] array items.
246: url_index_map_split_scsv
247:
248:
249: # Sort multiple items, flagging items that must be targeted using their
250: # database row id.
251: url_index_map_sort
252:
253:
254: TRUE
255: end
URL content mapping.
When not viewing all items on a single page, elements with the exact same database field value or title must be linked to using their unique database row id, in order to ensure accessibility.
This is not an issue for URL content references not listed below, as they eventually reduce to one of the cases below.
# File app/controllers/content_url_mapping.rb, line 268
268: def url_index_map_sort
269: # Sort the results, case insensitive.
270: if (@index['map'].size > 1)
271: @index['map'].sort! {
272: |a,b|
273: (a[0].to_s.length > 0) ? (a = a[0].down) : (a = '')
274: (b[0].to_s.length > 0) ? (b = b[0].down) : (b = '')
275: a <=> b
276: }
277: end
278:
279:
280: # Append fourth and fifth elements to each sub-array of @index['map'].
281: a = []
282: @index['map'].each { |i| i = i.concat([nil]) while (i.size < 5) ; a << i }
283: @index['map'] = a
284:
285:
286: if (!@index['all'] && (@index['map'].size > 1) &&
287: (URL_FIELD == @index['reference_type'] ||
288: URL_FIELD_VALUE == @index['reference_type'] ||
289: URL_TITLE == @index['reference_type']))
290: # Create an array containing only the <item data> (i.e. first)
291: # element of each sub-array.
292: map_size = @index['map'].size
293: map_transpose = @index['map'].transpose[0]
294:
295: # Work array.
296: a = []
297:
298: # Construct a copy of @index['map'] in a[] that excludes
299: # elements with <item data> values that already exist in a[].
300: for map in @index['map']
301: url_encoded_value = url_encode(map[0])
302:
303: # Count the number of times that this value occurs in the index map.
304: value_count = map_transpose.select{|v| v.down == map[0].down}.size
305:
306: # If the current value being processed matches the requested URL, or
307: # if all values in the index map array are the same, then link to all
308: # results by database row ID.
309: if ((@index['path_info'] == "#{map[1]}/#{url_encoded_value}") ||
310: (value_count == map_size))
311: a << [map[0], map[1], map[2],
312: map[0] + URL_CONTENT_INDEX_ITEM_ID.parse_data(map[2]), TRUE]
313: # Otherwise, if the work array is empty or does not contain the value
314: # being processed, then continue.
315: elsif ((0 == a.size) || !a.transpose[0].has(map[0], TRUE))
316: # If this value occurs more than once in the index map, or does not
317: # contain any special characters [i.e. the url_decode()'d version
318: # of the value matches the original value], then link to this item
319: # using only its value.
320: if ((value_count > 1) || (url_decode(url_encoded_value) == map[0]))
321: a << map
322: # Otherwise, link to this item including it's database row ID to
323: # ensure uniqueness.
324: else
325: a << [map[0], map[1], map[2], nil, TRUE]
326: end
327: end
328: end
329:
330: @index['map'] = a
331: end
332: end
URL content mapping.
If the <item data> (i.e. first) element of the @index[‘map’] array is a SCSV then split it into individual values, and append them to the @index[‘map’].
# File app/controllers/content_url_mapping.rb, line 342
342: def url_index_map_split_scsv
343: a = []
344:
345: # Process each element.
346: for map in @index['map']
347: # This is an SCSV value; process each value within the SCSV string,
348: # creating an individual array element for each individual value
349: # within a[].
350: if (map[0] =~ /#{SCSV}/)
351: map[0].split("#{SCSV}").each {|v| a << [v].concat(map.last(map.size-1))}
352: else
353: a << map
354: end
355: end
356:
357: @index['map'] = a
358: end
URL content mapping.
Extract the project page path and content reference from a URL, and retrieve all items for this URL mapping.
URL content references must be processed in the following order to avoid erroneous categorization:
# File app/controllers/content_url_mapping.rb, line 398
398: def url_parse_reference (
399: project_id,
400: project_host_id,
401: path_info,
402: content_type_name
403: )
404: @index['type'] = types(project_id, project_host_id, content_type_name)
405: @index['path_info'] = path_info
406: content_type_id = @index['type'].id
407: table_name = ContentItem.set_table(content_type_id)
408: sql_condition = ''
409:
410:
411: # Date-title reference.
412: if (url_split(URL_DATE_TITLE_REFERENCE))
413: url_resolve_date(1)
414: url_resolve_title(2)
415: @index['reference_type'] = URL_DATE
416: sql_condition = "date LIKE '" + @index['date'] + "%' " +
417: " AND title LIKE '" + @index['title'] + "'"
418:
419: # Date reference.
420: elsif (url_split(URL_DATE_REFERENCE))
421: url_resolve_date(1)
422: @index['reference_type'] = URL_DATE
423: sql_condition = "date LIKE '" + @index['date'] + "%'"
424:
425: # View all index page reference.
426: elsif (url_split(URL_ALL_INDEX_REFERENCE))
427: # View all items references employ the same index map generation logic
428: # as URL content type name references, the only difference between the
429: # two being the verbage and URL targets in the resulting HTML page.
430: @index['reference_type'] = URL_CONTENT_TYPE_NAME
431: @index['parts'][0] = @index['parts'][1]
432: @index['all'] = URL_ALL_INDEX
433:
434: # View all items reference.
435: elsif (url_split(URL_ALL_ITEMS_REFERENCE))
436: url_resolve_field(1, 2)
437: @index['reference_type'] = URL_FIELD_VALUE
438: @index['all'] = URL_ALL_ITEMS
439:
440: # Newest content item reference.
441: elsif (url_split(URL_NEWEST_ITEM_REFERENCE))
442: @index['reference_type'] = URL_NEWEST_CONTENT_ITEM
443: item(project_id,project_host_id,content_type_id,ContentItem.get_newest_id)
444:
445: # Oldest content item reference.
446: elsif (url_split(URL_OLDEST_ITEM_REFERENCE))
447: @index['reference_type'] = URL_OLDEST_CONTENT_ITEM
448: item(project_id,project_host_id,content_type_id,ContentItem.get_oldest_id)
449:
450: # Database value reference.
451: elsif (url_split(URL_FIELD_VALUE_REFERENCE))
452: url_resolve_field(1, 2, 3)
453: @index['reference_type'] = URL_FIELD_VALUE
454: # All fields other than 'id' may contain '%' characters in their
455: # value and must therefore be searched for using SQL 'LIKE'.
456: if ('id' == @index['field'])
457: sql_condition = "id = " + @index['value']
458: else
459: sql_condition = @index['field'] + " LIKE '" + @index['value'] +"'"
460: end
461:
462: # Database field reference.
463: elsif (url_split(URL_FIELD_REFERENCE))
464: url_resolve_field(2)
465: @index['reference_type'] = URL_FIELD
466:
467: # Content type name reference.
468: elsif (url_split(URL_CONTENT_TYPE_NAME_REFERENCE))
469: @index['reference_type'] = URL_CONTENT_TYPE_NAME
470: @index['parts'][0] = @index['parts'][1]
471:
472: # Title reference.
473: elsif (url_split(URL_TITLE_REFERENCE))
474: url_resolve_title(1)
475: @index['reference_type'] = URL_TITLE
476: sql_condition = "title LIKE '" + @index['title'] + "'"
477:
478: end
479:
480:
481: # This URL contained a content reference, so retrieve all content
482: # item ids, if they have not yet been obtained.
483: if (@index['parts'].size > 0)
484: @index['path'] = @index['parts'][0]
485:
486: # Search for the requested value.
487: if (0 == @index['type'].items.size)
488: (sql_condition.length > 0) ? (sql_and = ' AND ') : (sql_and = '')
489:
490: for content_item in ContentItem.find_by_sql("SELECT id
491: FROM #{table_name}
492: WHERE #{sql_condition}
493: #{sql_and}
494: active = 'TRUE'")
495: # Obtain the full database row for this content item.
496: item(project_id, project_host_id, content_type_id, content_item.id)
497: end
498: end
499: end
500:
501:
502: # Create an index map if there are active items for this reference.
503: if (@index['type'].items.size > 0)
504: url_get_index_map(project_id, project_host_id)
505: end
506:
507:
508: (@index['map'].size > 0) ? (TRUE) : (FALSE)
509: end
URL content mapping.
Convert a URL date reference to database datetime format, using the ’-’ character to separate date segments, so that it may be used as a search value for a database datetime column.
This method handles cases in which the URL is of the format:
For example:
# File app/controllers/content_url_mapping.rb, line 531
531: def url_resolve_date (
532: id
533: )
534: if (@index['parts'][id])
535: date = @index['parts'][id]
536: month = date.extract(URL_MONTH_REFERENCE)
537: day = date.extract(URL_DAY_REFERENCE)
538:
539:
540: # If the date is invalid then we will not return a content_reference.
541: if (!(month && month.to_i > MONTHS_IN_YEAR) ||
542: !(day && day.to_i > DAYS_IN_MONTH[month.to_i]))
543: # Extract the year from this date.
544: year = date.scan(/#{URL_YEAR_REFERENCE}/)
545: @index['year'] = year
546:
547:
548: # Convert single digit months to double digit format by prepending '0',
549: # so it may be used as a search value against a database datetime
550: # column.
551: if (month && 1 == month.length)
552: padded_month = '0' + month
553: date = date.gsub(/#{year}\/#{month}/,
554: "#{year}/#{padded_month}")
555: @index['month'] = padded_month
556: elsif (month)
557: @index['month'] = month
558: end
559:
560:
561: # Convert a single digit day to double digit format, as done for month.
562: if (day && 1 == day.length)
563: padded_day = '0' + day
564: date = date.gsub(/\/#{day}$/, '/' + padded_day)
565: @index['day'] = padded_day
566: elsif (day)
567: @index['day'] = day
568: end
569:
570:
571: # Convert from URL format to database datetime format.
572: @index['date'] = date.gsub(/\//, '-')
573: end
574:
575:
576: @index['field'] = 'date'
577: end
578:
579:
580: TRUE
581: end
URL content mapping.
Obtain a database field, value, and id from the requested URL.
This method handles cases in which the URL is of the format:
For example:
# File app/controllers/content_url_mapping.rb, line 599
599: def url_resolve_field (
600: *arguments
601: )
602: @index['field'] = @index['parts'][arguments[0]]
603:
604: if (arguments[1])
605: if ('id' == @index['field'])
606: @index['value'] = @index['parts'][arguments[1]]
607: else
608: @index['value'] = url_decode(@index['parts'][arguments[1]], TRUE)
609: end
610: end
611:
612: if (arguments[2] && @index['parts'][arguments[2]])
613: @index['field'] = 'id'
614: @index['value'] = @index['parts'][arguments[2]]
615: end
616:
617: TRUE
618: end
URL content mapping.
Convert a URL title reference to the format used by the title field of a content table.
This method handles cases in which the URL is of the format:
For example:
# File app/controllers/content_url_mapping.rb, line 635
635: def url_resolve_title (
636: id
637: )
638: @index['title'] = url_decode(@index['parts'][id], TRUE)
639: end
URL content mapping.
Divide a URL content reference into it‘s components:
# File app/controllers/content_url_mapping.rb, line 649
649: def url_split (
650: regex
651: )
652: if (matches = @index['path_info'].match(/#{regex}/))
653: @index['parts'] = matches.to_a
654: @index['parts'][0] = url_extract_content_type_name(@index['path_info'])
655: return TRUE
656: else
657: return FALSE
658: end
659: end