downloads category and sub-catgory

Post

Posted
Rating:
#1311 (In Topic #318)
Running RC25, in the Home→ Content Management→ Downloads→ Edit download category, the relation of categories and sub-categories were in order. But in Home→ Downloads home,  two of the three view buttons lead to sub-categories. Can I fix it in the phpMyAdmin without doing them again? why it is "untitled"? Where can I change it to something more meaningful?

The Category (C) tree is somthing like this
C1 http://www.tctfhlc-hk.net/index.php?page=downloads&type=browse&id=untitled-6
 C11
 C12
C2 http://www.tctfhlc-hk.net/index.php?page=downloads&type=browse&id=untitled-4
 C21
 C22
C3 http://www.tctfhlc-hk.net/index.php?page=downloads&type=browse&id=untitled
 C31
 C32

Post

Posted
Rating:
#1316
Hi there,

I'm taking a look at this now.

It might help if you can get me a login to your site so I can see more closely, or a screenshot at least.

However I think the URL issue is due to Chinese characters not being good for URLs. I'm giving that some thought now.

Post

Posted
Rating:
#1318
It's exciting to see a Chinese-language site :) .

I've added transliteration:

Code (php)

  1. <?php /*
  2.  
  3.  Composr
  4.  Copyright (c) ocProducts, 2004-2016
  5.  
  6.  See text/EN/licence.txt for full licencing information.
  7.  
  8.  
  9.  NOTE TO PROGRAMMERS:
  10.    Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
  11.    **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****
  12.  
  13. */
  14.  
  15. /**
  16.  * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
  17.  * @copyright  ocProducts Ltd
  18.  * @package    core
  19.  */
  20.  
  21. /**
  22.  * Change whatever global context that is required in order to run from a different context.
  23.  *
  24.  * @sets_input_state
  25.  *
  26.  * @param  array $new_get The URL component map (must contain 'page').
  27.  * @param  ID_TEXT $new_zone The zone.
  28.  * @param  ID_TEXT $new_current_script The running script.
  29.  * @param  boolean $erase_keep_also Whether to get rid of keep_ variables in current URL.
  30.  * @return array A list of parameters that would be required to be passed back to reset the state.
  31.  */
  32. function set_execution_context($new_get, $new_zone = '_SEARCH', $new_current_script = 'index', $erase_keep_also = false)
  33. {
  34.     $old_get = $_GET;
  35.     $old_zone = get_zone_name();
  36.     $old_current_script = current_script();
  37.  
  38.     foreach ($_GET as $key => $val) {
  39.         if (is_integer($key)) {
  40.             $key = strval($key);
  41.         }
  42.  
  43.         if ((substr($key, 0, 5) != 'keep_') || ($erase_keep_also)) {
  44.             unset($_GET[$key]);
  45.         }
  46.     }
  47.  
  48.     foreach ($new_get as $key => $val) {
  49.         $_GET[$key] = is_integer($val) ? strval($val) : $val;
  50.     }
  51.  
  52.     global $RELATIVE_PATH, $ZONE, $SELF_URL_CACHED;
  53.     $RELATIVE_PATH = ($new_zone == '_SEARCH') ? get_page_zone(get_page_name()) : $new_zone;
  54.     if ($new_zone != $old_zone) {
  55.         $ZONE = null; // So zone details will have to reload
  56.     }
  57.     $SELF_URL_CACHED = null;
  58.  
  59.     global $PAGE_NAME_CACHE;
  60.     $PAGE_NAME_CACHE = null;
  61.     global $RUNNING_SCRIPT_CACHE, $WHAT_IS_RUNNING_CACHE;
  62.     $RUNNING_SCRIPT_CACHE = array();
  63.     $WHAT_IS_RUNNING_CACHE = $new_current_script;
  64.  
  65.     return array($old_get, $old_zone, $old_current_script, true);
  66. }
  67.  
  68. /**
  69.  * Map spaces to %20 and put http:// in front of URLs starting www.
  70.  *
  71.  * @param  URLPATH $url The URL to fix
  72.  * @return URLPATH The fixed result
  73.  */
  74. function remove_url_mistakes($url)
  75. {
  76.     if (substr($url, 0, 4) == 'www.') {
  77.         $url = 'http://' . $url;
  78.     }
  79.     $url = @html_entity_decode($url, ENT_NOQUOTES);
  80.     $url = str_replace(' ', '%20', $url);
  81.     $url = preg_replace('#keep_session=\w*#', 'filtered=1', $url);
  82.     return $url;
  83. }
  84.  
  85. /**
  86.  * Get hidden fields for a form representing 'keep_x'. If we are having a GET form instead of a POST form, we need to do this. This function also encodes the page name, as we'll always want that.
  87.  *
  88.  * @param  ID_TEXT $page The page for the form to go to (blank: don't attach)
  89.  * @param  boolean $keep_all Whether to keep all elements of the current URL represented in this form (rather than just the keep_ fields, and page)
  90.  * @param  ?array $exclude A list of parameters to exclude (null: don't exclude any)
  91.  * @return Tempcode The builtup hidden form fields
  92.  *
  93.  * @ignore
  94.  */
  95. function _build_keep_form_fields($page = '', $keep_all = false, $exclude = null)
  96. {
  97.     if (is_null($exclude)) {
  98.         $exclude = array();
  99.     }
  100.  
  101.     if ($page == '_SELF') {
  102.         $page = get_page_name();
  103.     }
  104.     $out = new Tempcode();
  105.  
  106.     if (count($_GET) > 0) {
  107.         foreach ($_GET as $key => $val) {
  108.             $process_for_key = ((substr($key, 0, 5) == 'keep_') || ($keep_all)) && (!in_array($key, $exclude)) && ($key != 'page') && (!skippable_keep($key, $val));
  109.  
  110.             if (is_array($val)) {
  111.                 foreach ($val as $_key => $_val) { // We'll only support one level deep. Also no keep parameter array support.
  112.                     if (get_magic_quotes_gpc()) {
  113.                         $_val = stripslashes($_val);
  114.                     }
  115.  
  116.                     if ($process_for_key) {
  117.                         $out->attach(form_input_hidden($key . '[' . $_key . ']', $_val));
  118.                     }
  119.                 }
  120.             } else {
  121.                 if (!is_string($val)) {
  122.                     continue;
  123.                 }
  124.  
  125.                 if (is_integer($key)) {
  126.                     $key = strval($key);
  127.                 }
  128.  
  129.                 if (get_magic_quotes_gpc()) {
  130.                     $val = stripslashes($val);
  131.                 }
  132.  
  133.                 if ($process_for_key) {
  134.                     $out->attach(form_input_hidden($key, $val));
  135.                 }
  136.             }
  137.         }
  138.     }
  139.     if ($page != '') {
  140.         $out->attach(form_input_hidden('page', $page));
  141.     }
  142.     return $out;
  143. }
  144.  
  145. /**
  146.  * Recurser helper function for _build_keep_post_fields.
  147.  *
  148.  * @param  ID_TEXT $key Key name to put value under
  149.  * @param  mixed $value Value (string or array)
  150.  * @return string The builtup hidden form fields
  151.  *
  152.  * @ignore
  153.  */
  154. function _fixed_post_parser($key, $value)
  155. {
  156.     $out = '';
  157.  
  158.     if (!is_string($key)) {
  159.         $key = strval($key);
  160.     }
  161.  
  162.     if (is_array($value)) {
  163.         foreach ($value as $k => $v) {
  164.             if (is_string($k)) {
  165.                 $out .= _fixed_post_parser($key . '[' . $k . ']', $v);
  166.             } else {
  167.                 $out .= _fixed_post_parser($key . '[' . strval($k) . ']', $v);
  168.             }
  169.         }
  170.     } else {
  171.         if (get_magic_quotes_gpc()) {
  172.             $value = stripslashes($value);
  173.         }
  174.  
  175.         $out .= static_evaluate_tempcode(form_input_hidden($key, is_string($value) ? $value : strval($value)));
  176.     }
  177.  
  178.     return $out;
  179. }
  180.  
  181. /**
  182.  * Relay all POST variables for this URL, to the URL embedded in the form.
  183.  *
  184.  * @param  ?array $exclude A list of parameters to exclude (null: exclude none)
  185.  * @param  boolean $force_everything Force field labels and descriptions to copy through even when there are huge numbers of parameters
  186.  * @return Tempcode The builtup hidden form fields
  187.  *
  188.  * @ignore
  189.  */
  190. function _build_keep_post_fields($exclude = null, $force_everything = false)
  191. {
  192.     $out = '';
  193.     foreach ($_POST as $key => $val) {
  194.         if (is_integer($key)) {
  195.             $key = strval($key);
  196.         }
  197.  
  198.         if (((!is_null($exclude)) && (in_array($key, $exclude))) || ($key == 'session_id'/*for spam blackhole*/)) {
  199.             continue;
  200.         }
  201.  
  202.         if (count($_POST) > 80 && !$force_everything) {
  203.             if (substr($key, 0, 14) == 'tick_on_form__') {
  204.                 continue;
  205.             }
  206.             if (substr($key, 0, 11) == 'label_for__') {
  207.                 continue;
  208.             }
  209.             if (substr($key, 0, 9) == 'require__') {
  210.                 continue;
  211.             }
  212.         }
  213.  
  214.         $out .= _fixed_post_parser($key, $val);
  215.     }
  216.     return make_string_tempcode($out);
  217. }
  218.  
  219. /**
  220.  * Takes a URL, and converts it into a file system storable filename. This is used to cache URL contents to the servers filesystem.
  221.  *
  222.  * @param  URLPATH $url_full The URL to convert to an encoded filename
  223.  * @return string A usable filename based on the URL
  224.  *
  225.  * @ignore
  226.  */
  227. function _url_to_filename($url_full)
  228. {
  229.     $bad_chars = array('!', '/', '\\', '?', '*', '<', '>', '|', '"', ':', '%', ' ');
  230.     $new_name = $url_full;
  231.     foreach ($bad_chars as $bad_char) {
  232.         $good_char = '!' . strval(ord($bad_char));
  233.         if ($bad_char == ':') {
  234.             $good_char = ';'; // So page_links save nice
  235.         }
  236.         $new_name = str_replace($bad_char, $good_char, $new_name);
  237.     }
  238.  
  239.     if (strlen($new_name) <= 200/*technically 256 but something may get put on the start, so be cautious*/) {
  240.         return $new_name;
  241.     }
  242.  
  243.     // Non correspondance, but at least we have something
  244.     if (strpos($new_name, '.') === false) {
  245.         return md5($new_name);
  246.     }
  247.     return md5($new_name) . '.' . get_file_extension($new_name);
  248. }
  249.  
  250. /**
  251.  * Take a URL and base-URL, and fully qualify the URL according to it.
  252.  *
  253.  * @param  URLPATH $url The URL to fully qualified
  254.  * @param  URLPATH $url_base The base-URL
  255.  * @return URLPATH Fully qualified URL
  256.  *
  257.  * @ignore
  258.  */
  259. function _qualify_url($url, $url_base)
  260. {
  261.     require_code('obfuscate');
  262.     $mto = mailto_obfuscated();
  263.     if (($url != '') && ($url[0] != '#') && (substr($url, 0, 5) != 'data:') && (substr($url, 0, 7) != 'mailto:') && (substr($url, 0, strlen($mto)) != $mto)) {
  264.         if (url_is_local($url)) {
  265.             if ($url[0] == '/') {
  266.                 $parsed = @parse_url($url_base);
  267.                 if ($parsed === false) {
  268.                     return '';
  269.                 }
  270.                 if (!array_key_exists('scheme', $parsed)) {
  271.                     $parsed['scheme'] = 'http';
  272.                 }
  273.                 if (!array_key_exists('host', $parsed)) {
  274.                     $parsed['host'] = 'localhost';
  275.                 }
  276.                 if (substr($url, 0, 2) == '//') {
  277.                     $url = $parsed['scheme'] . ':' . $url;
  278.                 } else {
  279.                     $url = $parsed['scheme'] . '://' . $parsed['host'] . (array_key_exists('port', $parsed) ? (':' . $parsed['port']) : '') . $url;
  280.                 }
  281.             } else {
  282.                 $url = $url_base . '/' . $url;
  283.             }
  284.         }
  285.     }
  286.  
  287.     $url = str_replace('/./', '/', $url);
  288.     $pos = strpos($url, '/../');
  289.     while ($pos !== false) {
  290.         $pos_2 = strrpos(substr($url, 0, $pos - 1), '/');
  291.         if ($pos_2 === false) {
  292.             break;
  293.         }
  294.         $url = substr($url, 0, $pos_2) . '/' . substr($url, $pos + 4);
  295.         $pos = strpos($url, '/../');
  296.     }
  297.  
  298.     return $url;
  299. }
  300.  
  301. /**
  302.  * Convert a URL to a local file path.
  303.  *
  304.  * @param  URLPATH $url The value to convert
  305.  * @return ?PATH File path (null: is not local)
  306.  * @ignore
  307.  */
  308. function _convert_url_to_path($url)
  309. {
  310.     if (strpos($url, '?') !== false) {
  311.         return null;
  312.     }
  313.     if ((strpos($url, '://') === false) || (substr($url, 0, strlen(get_base_url()) + 1) == get_base_url() . '/') || (substr($url, 0, strlen(get_custom_base_url()) + 1) == get_custom_base_url() . '/')) {
  314.         if (substr($url, 0, strlen(get_base_url()) + 1) == get_base_url() . '/') {
  315.             $file_path_stub = urldecode(substr($url, strlen(get_base_url()) + 1));
  316.         } elseif (substr($url, 0, strlen(get_custom_base_url()) + 1) == get_custom_base_url() . '/') {
  317.             $file_path_stub = urldecode(substr($url, strlen(get_custom_base_url()) + 1));
  318.         } else {
  319.             $file_path_stub = urldecode($url);
  320.         }
  321.         if (((substr($file_path_stub, 0, 7) == 'themes/') && (substr($file_path_stub, 0, 15) != 'themes/default/')) || (substr($file_path_stub, 0, 8) == 'uploads/') || (strpos($file_path_stub, '_custom/') !== false)) {
  322.             $_file_path_stub = get_custom_file_base() . '/' . $file_path_stub;
  323.             if (!is_file($_file_path_stub)) {
  324.                 $_file_path_stub = get_file_base() . '/' . $file_path_stub;
  325.             }
  326.         } else {
  327.             $_file_path_stub = get_file_base() . '/' . $file_path_stub;
  328.         }
  329.  
  330.         if (!is_file($_file_path_stub)) {
  331.             return null;
  332.         }
  333.  
  334.         return $_file_path_stub;
  335.     }
  336.  
  337.     return null;
  338. }
  339.  
  340. /**
  341.  * Sometimes users don't enter full URLs but do intend for them to be absolute. This code tries to see what relative URLs are actually absolute ones, via an algorithm. It then fixes the URL.
  342.  *
  343.  * @param  URLPATH $in The URL to fix
  344.  * @return URLPATH The fixed URL (or original one if no fix was needed)
  345.  * @ignore
  346.  */
  347. function _fixup_protocolless_urls($in)
  348. {
  349.     if ($in == '') {
  350.         return $in;
  351.     }
  352.  
  353.     $in = remove_url_mistakes($in); // Chain in some other stuff
  354.  
  355.     if (strpos($in, ':') !== false) {
  356.         return $in; // Absolute (e.g. http:// or mailto:)
  357.     }
  358.  
  359.     if (substr($in, 0, 1) == '#') {
  360.         return $in;
  361.     }
  362.     if (substr($in, 0, 1) == '%') {
  363.         return $in;
  364.     }
  365.     if (substr($in, 0, 1) == '{') {
  366.         return $in;
  367.     }
  368.  
  369.     // Rule 1: // If we have a dot somewhere before a slash, then this dot is likely part of a domain name (not a file extension)- thus we have an absolute URL.
  370.     if (preg_match('#\..*/#', $in) != 0) {
  371.         return 'http://' . $in; // Fix it
  372.     }
  373.     // Rule 2: // If we have no slashes and we don't recognise a file type then they've probably just entered a domain name- thus we have an absolute URL.
  374.     if ((preg_match('#^[^/]+$#', $in) != 0) && (preg_match('#\.(php|htm|asp|jsp|swf|gif|png|jpg|jpe|txt|pdf|odt|ods|odp|doc|mdb|xls|ppt|xml|rss|ppt|svg|wrl|vrml|gif|psd|rtf|bmp|avi|mpg|mpe|webm|mp4|mov|wmv|ram|rm|asf|ra|wma|wav|mp3|ogg|torrent|csv|ttf|tar|gz|rar|bz2)#', $in) == 0)) {
  375.         return 'http://' . $in . '/'; // Fix it
  376.     }
  377.  
  378.     return $in; // Relative
  379. }
  380.  
  381. /**
  382.  * Convert a local URL to a page-link.
  383.  *
  384.  * @param  URLPATH $url The URL to convert. Note it may not be a URL Scheme, and it must be based on the local base URL (else failure WILL occur).
  385.  * @param  boolean $abs_only Whether to only convert absolute URLs. Turn this on if you're not sure what you're passing is a URL not and you want to be extra safe.
  386.  * @param  boolean $perfect_only Whether to only allow perfect conversions.
  387.  * @return string The page-link (blank: could not convert).
  388.  *
  389.  * @ignore
  390.  */
  391. function _url_to_page_link($url, $abs_only = false, $perfect_only = true)
  392. {
  393.     if (($abs_only) && (substr($url, 0, 7) != 'http://') && (substr($url, 0, 8) != 'https://')) {
  394.         return '';
  395.     }
  396.  
  397.     // Try and strip any variants of the base URL from our $url variable, to make it relative
  398.     $non_www_base_url = str_replace('https://www.', 'https://', str_replace('http://www.', 'http://', get_base_url()));
  399.     $www_base_url = str_replace('https://', 'https://www.', str_replace('http://', 'http://www.', get_base_url()));
  400.     $url = preg_replace('#^' . preg_quote(get_base_url() . '/', '#') . '#', '', $url);
  401.     $url = preg_replace('#^' . preg_quote($non_www_base_url . '/', '#') . '#', '', $url);
  402.     $url = preg_replace('#^' . preg_quote($www_base_url . '/', '#') . '#', '', $url);
  403.     if (substr($url, 0, 7) == 'http://') {
  404.         return '';
  405.     }
  406.     if (substr($url, 0, 8) == 'https://') {
  407.         return '';
  408.     }
  409.     if (substr($url, 0, 1) != '/') {
  410.         $url = '/' . $url;
  411.     }
  412.  
  413.     // Parse the URL
  414.     $parsed_url = @parse_url($url);
  415.     if ($parsed_url === false) {
  416.         require_code('site');
  417.         attach_message(do_lang_tempcode('HTTP_DOWNLOAD_BAD_URL', escape_html($url)), 'warn');
  418.         return '';
  419.     }
  420.  
  421.     // Work out the zone
  422.     $slash_pos = strpos($parsed_url['path'], '/', 1);
  423.     $zone = ($slash_pos !== false) ? substr($parsed_url['path'], 1, $slash_pos - 1) : '';
  424.     if (!in_array($zone, find_all_zones())) {
  425.         $zone = '';
  426.         $slash_pos = false;
  427.     }
  428.     $parsed_url['path'] = ($slash_pos === false) ? substr($parsed_url['path'], 1) : substr($parsed_url['path'], $slash_pos + 1); // everything AFTER the zone
  429.     $parsed_url['path'] = preg_replace('#/index\.php$#', '', $parsed_url['path']);
  430.     $attributes = array();
  431.     $attributes['page'] = ''; // hopefully will get overwritten with a real one
  432.  
  433.     // Convert URL Scheme path info into extra implied attribute data
  434.     require_code('url_remappings');
  435.     $does_match = false;
  436.     foreach (array('PG', 'HTM', 'SIMPLE', 'RAW') as $url_scheme) {
  437.         $mappings = get_remappings($url_scheme);
  438.         foreach ($mappings as $mapping) { // e.g. array(array('page' => 'wiki', 'id' => null), 'pg/s/ID', true),
  439.             if (is_null($mapping)) {
  440.                 continue;
  441.             }
  442.  
  443.             list($params, $match_string,) = $mapping;
  444.             $match_string_pattern = preg_replace('#[A-Z]+#', '[^\&\?]+', preg_quote($match_string)); // Turn match string into a regexp
  445.  
  446.             $does_match = (preg_match('#^' . $match_string_pattern . '#', $parsed_url['path']) != 0);
  447.             if ($does_match) {
  448.                 $attributes = array_merge($attributes, $params);
  449.  
  450.                 if ($url_scheme == 'HTM') {
  451.                     if (strpos($parsed_url['path'], '.htm') === false) {
  452.                         continue;
  453.                     }
  454.  
  455.                     $_match_string = preg_replace('#\.htm$#', '', $match_string);
  456.                     $_path = preg_replace('#\.htm($|\?)#', '', $parsed_url['path']);
  457.                 } else {
  458.                     if (strpos($parsed_url['path'], '.htm') !== false) {
  459.                         continue;
  460.                     }
  461.  
  462.                     $_match_string = $match_string;
  463.                     $_path = $parsed_url['path'];
  464.                 }
  465.  
  466.                 $bits_pattern = explode('/', $_match_string);
  467.                 $bits_real = explode('/', $_path, count($bits_pattern));
  468.  
  469.                 foreach ($bits_pattern as $i => $bit) {
  470.                     if ((strtoupper($bit) == $bit) && (array_key_exists(strtolower($bit), $params)) && (is_null($params[strtolower($bit)]))) {
  471.                         $attributes[strtolower($bit)] = $bits_real[$i];
  472.                     }
  473.                 }
  474.  
  475.                 foreach ($attributes as &$attribute) {
  476.                     $attribute = cms_url_decode_post_process(urldecode($attribute));
  477.                 }
  478.  
  479.                 break 2;
  480.             }
  481.         }
  482.     }
  483.     if (!$does_match) {
  484.         return ''; // No match was found
  485.     }
  486.  
  487.     // Parse query string component into the waiting (and partly-filled-already) attribute data array
  488.     if (array_key_exists('query', $parsed_url)) {
  489.         $bits = explode('&', $parsed_url['query']);
  490.         foreach ($bits as $bit) {
  491.             $_bit = explode('=', $bit, 2);
  492.  
  493.             if (count($_bit) == 2) {
  494.                 $attributes[$_bit[0]] = cms_url_decode_post_process($_bit[1]);
  495.                 if (strpos($attributes[$_bit[0]], ':') !== false) {
  496.                     if ($perfect_only) {
  497.                         return ''; // Could not convert this URL to a page-link, because it contains a colon
  498.                     }
  499.                     unset($attributes[$_bit[0]]);
  500.                 }
  501.             }
  502.         }
  503.     }
  504.  
  505.     require_code('site');
  506.     if (_request_page($attributes['page'], $zone) === false) {
  507.         return '';
  508.     }
  509.  
  510.     $page = fix_page_name_dashing($zone, $attributes['page']);
  511.  
  512.     // Put it together
  513.     $page_link = $zone . ':' . $page;
  514.     if (array_key_exists('type', $attributes)) {
  515.         $page_link .= ':' . $attributes['type'];
  516.     } elseif (array_key_exists('id', $attributes)) {
  517.         $page_link .= ':';
  518.     }
  519.     if (array_key_exists('id', $attributes)) {
  520.         $page_link .= ':' . $attributes['id'];
  521.     }
  522.     foreach ($attributes as $key => $val) {
  523.         if (!is_string($val)) {
  524.             $val = strval($val);
  525.         }
  526.  
  527.         if (($key != 'page') && ($key != 'type') && ($key != 'id')) {
  528.             $page_link .= ':' . $key . '=' . cms_url_encode($val);
  529.         }
  530.     }
  531.  
  532.     // Hash bit?
  533.     if (array_key_exists('fragment', $parsed_url)) {
  534.         $page_link .= '#' . $parsed_url['fragment'];
  535.     }
  536.  
  537.     return $page_link;
  538. }
  539.  
  540. /**
  541.  * Convert a local page file path to a written page-link.
  542.  *
  543.  * @param  string $page The path.
  544.  * @return string The page-link (blank: could not convert).
  545.  *
  546.  * @ignore
  547.  */
  548. function _page_path_to_page_link($page)
  549. {
  550.     if ((substr($page, 0, 1) == '/') && (substr($page, 0, 6) != '/pages')) {
  551.         $page = substr($page, 1);
  552.     }
  553.     $matches = array();
  554.     if (preg_match('#^([^/]*)/?pages/([^/]+)/(\w\w/)?([^/\.]+)\.(php|txt|htm)$#', $page, $matches) == 1) {
  555.         $page2 = $matches[1] . ':' . $matches[4];
  556.         if (($matches[2] == 'comcode') || ($matches[2] == 'comcode_custom')) {
  557.             if (file_exists(get_custom_file_base() . '/' . $page)) {
  558.                 $file = file_get_contents(get_custom_file_base() . '/' . $page);
  559.                 if (preg_match('#\[title\](.*)\[/title\]#U', $file, $matches) != 0) {
  560.                     $page2 .= ' (' . $matches[1] . ')';
  561.                 } elseif (preg_match('#\[title=[\'"]?1[\'"]?\](.*)\[/title\]#U', $file, $matches) != 0) {
  562.                     $page2 .= ' (' . $matches[1] . ')';
  563.                 }
  564.                 $page2 = preg_replace('#\[[^\[\]]*\]#', '', $page2);
  565.             }
  566.         }
  567.     } else {
  568.         $page2 = '';
  569.     }
  570.  
  571.     return $page2;
  572. }
  573.  
  574. /**
  575.  * Called from 'find_id_moniker'. We tried to lookup a moniker, found a hook, but found no stored moniker. So we'll try and autogenerate one.
  576.  *
  577.  * @param  array $ob_info The hooks info profile.
  578.  * @param  array $url_parts The URL component map (must contain 'page', 'type', and 'id' if this function is to do anything).
  579.  * @param  ID_TEXT $zone The URL zone name (only used for Comcode Page URL monikers).
  580.  * @return ?string The moniker ID (null: error generating it somehow, can not do it)
  581.  */
  582. function autogenerate_new_url_moniker($ob_info, $url_parts, $zone)
  583. {
  584.     $effective_id = ($url_parts['type'] == '') ? $url_parts['page'] : $url_parts['id'];
  585.  
  586.     $bak = $GLOBALS['NO_DB_SCOPE_CHECK'];
  587.     $GLOBALS['NO_DB_SCOPE_CHECK'] = true;
  588.     require_code('content');
  589.     $select = array();
  590.     append_content_select_for_id($select, $ob_info);
  591.     if (substr($ob_info['title_field'], 0, 5) != 'CALL:') {
  592.         $select[] = $ob_info['title_field'];
  593.     }
  594.     if ($ob_info['parent_category_field'] !== null) {
  595.         $select[] = $ob_info['parent_category_field'];
  596.     }
  597.     $db = ((substr($ob_info['table'], 0, 2) != 'f_') || (get_forum_type() == 'none')) ? $GLOBALS['SITE_DB'] : $GLOBALS['FORUM_DB'];
  598.     $where = get_content_where_for_str_id($effective_id, $ob_info);
  599.     if (isset($where['the_zone'])) {
  600.         $where['the_zone'] = $zone;
  601.     }
  602.     $_moniker_src = $db->query_select($ob_info['table'], $select, $where); // NB: For Comcode pages visited, this won't return anything -- it will become more performant when the page actually loads, so the moniker won't need redoing each time
  603.     $GLOBALS['NO_DB_SCOPE_CHECK'] = $bak;
  604.     if (!array_key_exists(0, $_moniker_src)) {
  605.         return null; // been deleted?
  606.     }
  607.  
  608.     if ($ob_info['id_field_numeric']) {
  609.         if (substr($ob_info['title_field'], 0, 5) == 'CALL:') {
  610.             $moniker_src = call_user_func(trim(substr($ob_info['title_field'], 5)), $url_parts);
  611.         } else {
  612.             if ($ob_info['title_field_dereference']) {
  613.                 $moniker_src = get_translated_text($_moniker_src[0][$ob_info['title_field']]);
  614.             } else {
  615.                 $moniker_src = $_moniker_src[0][$ob_info['title_field']];
  616.             }
  617.         }
  618.     } else {
  619.         $moniker_src = $effective_id;
  620.     }
  621.  
  622.     if ($moniker_src == '') {
  623.         $moniker_src = 'untitled';
  624.     }
  625.  
  626.     return suggest_new_idmoniker_for($url_parts['page'], isset($url_parts['type']) ? $url_parts['type'] : '', $url_parts['id'], $zone, $moniker_src, true);
  627. }
  628.  
  629. /**
  630.  * Called when content is added, or edited/moved, based upon a new form field that specifies what moniker to use.
  631.  *
  632.  * @param  ID_TEXT $page Page name.
  633.  * @param  ID_TEXT $type Screen type code.
  634.  * @param  ID_TEXT $id Resource ID.
  635.  * @param  ID_TEXT $zone The URL zone name (only used for Comcode Page URL monikers).
  636.  * @param  string $moniker_src String from which a moniker will be chosen (may not be blank).
  637.  * @param  boolean $is_new Whether we are sure this is a new moniker (makes things more efficient, saves a query).
  638.  * @param  ?string $moniker Actual moniker to use (null: generate from $moniker_src). Usually this is left null.
  639.  * @return string The chosen moniker.
  640.  */
  641. function suggest_new_idmoniker_for($page, $type, $id, $zone, $moniker_src, $is_new = false, $moniker = null)
  642. {
  643.     if (get_option('url_monikers_enabled') == '0') {
  644.         return '';
  645.     }
  646.  
  647.     static $force_called = array();
  648.     $ref = $zone . ':' . $page . ':' . $type . ':' . $id;
  649.     if ($moniker !== null) {
  650.         $force_called[$ref] = $moniker;
  651.     } else {
  652.         if (isset($force_called[$ref])) {
  653.             return $force_called[$ref];
  654.         }
  655.     }
  656.  
  657.     if (!$is_new) {
  658.         $manually_chosen = $GLOBALS['SITE_DB']->query_select_value_if_there('url_id_monikers', 'm_moniker', array('m_manually_chosen' => 1, 'm_resource_page' => $page, 'm_resource_type' => $type, 'm_resource_id' => $id));
  659.         if ($manually_chosen !== null) {
  660.             return $manually_chosen;
  661.         }
  662.  
  663.         // Deprecate old one if already exists
  664.         $old = $GLOBALS['SITE_DB']->query_select_value_if_there('url_id_monikers', 'm_moniker', array('m_resource_page' => $page, 'm_resource_type' => $type, 'm_resource_id' => $id, 'm_deprecated' => 0), 'ORDER BY id DESC');
  665.         if (!is_null($old)) {
  666.             // See if it is same as current
  667.             if ($moniker === null) {
  668.                 $scope = _give_moniker_scope($page, $type, $id, $zone, '');
  669.                 $moniker = $scope . _choose_moniker($page, $type, $id, $moniker_src, $old, $scope);
  670.             }
  671.             if ($moniker == $old) {
  672.                 return $old; // hmm, ok it can stay actually
  673.             }
  674.  
  675.             // It's not. Although, the later call to _choose_moniker will allow us to use the same stem as the current active one, or even re-activate an old deprecated one, so long as it is on this same m_resource_page/m_resource_page/m_resource_id.
  676.  
  677.             // Deprecate
  678.             $GLOBALS['SITE_DB']->query_update('url_id_monikers', array('m_deprecated' => 1), array('m_resource_page' => $page, 'm_resource_type' => $type, 'm_resource_id' => $id, 'm_deprecated' => 0), '', 1); // Deprecate
  679.  
  680.             // Deprecate anything underneath
  681.             global $CONTENT_OBS;
  682.             load_moniker_hooks();
  683.             $looking_for = '_SEARCH:' . $page . ':' . $type . ':_WILD';
  684.             $ob_info = isset($CONTENT_OBS[$looking_for]) ? $CONTENT_OBS[$looking_for] : null;
  685.             if (!is_null($ob_info)) {
  686.                 $parts = explode(':', $ob_info['view_page_link_pattern']);
  687.                 $category_page = $parts[1];
  688.                 $GLOBALS['SITE_DB']->query('UPDATE ' . get_table_prefix() . 'url_id_monikers SET m_deprecated=1 WHERE ' . db_string_equal_to('m_resource_page', $category_page) . ' AND m_moniker LIKE \'' . db_encode_like($old . '/%') . '\''); // Deprecate
  689.             }
  690.         }
  691.     }
  692.  
  693.     if ($moniker === null) {
  694.         if (is_numeric($moniker_src)) {
  695.             $moniker = $id;
  696.         } else {
  697.             $scope = _give_moniker_scope($page, $type, $id, $zone, '');
  698.             $moniker = $scope . _choose_moniker($page, $type, $id, $moniker_src, null, $scope);
  699.  
  700.             if (($page == 'news') && ($type == 'view') && (get_value('google_news_urls') === '1')) {
  701.                 $moniker .= '-' . str_pad($id, 3, '0', STR_PAD_LEFT);
  702.             }
  703.         }
  704.     }
  705.  
  706.     // Insert
  707.     $GLOBALS['SITE_DB']->query_delete('url_id_monikers', array(    // It's possible we're re-activating a deprecated one
  708.                                                                    'm_resource_page' => $page,
  709.                                                                    'm_resource_type' => $type,
  710.                                                                    'm_resource_id' => $id,
  711.                                                                    'm_moniker' => $moniker,
  712.     ), '', 1);
  713.     $GLOBALS['SITE_DB']->query_insert('url_id_monikers', array(
  714.         'm_resource_page' => $page,
  715.         'm_resource_type' => $type,
  716.         'm_resource_id' => $id,
  717.         'm_moniker' => $moniker,
  718.         'm_moniker_reversed' => strrev($moniker),
  719.         'm_deprecated' => 0,
  720.         'm_manually_chosen' => 0,
  721.     ));
  722.  
  723.     global $LOADED_MONIKERS_CACHE;
  724.     $LOADED_MONIKERS_CACHE = array();
  725.  
  726.     return $moniker;
  727. }
  728.  
  729. /**
  730.  * Delete an old moniker, and place a new one.
  731.  *
  732.  * @param  ID_TEXT $page Page name.
  733.  * @param  ID_TEXT $type Screen type code.
  734.  * @param  ID_TEXT $id Resource ID.
  735.  * @param  string $moniker_src String from which a moniker will be chosen (may not be blank).
  736.  * @param  ?string $no_exists_check_for Whether to skip the exists check for a certain moniker (will be used to pass "existing self" for edits) (null: nothing existing to check against).
  737.  * @param  ?string $scope_context Where the moniker will be placed in the moniker URL tree (null: unknown, so make so no duplicates anywhere).
  738.  * @return string Chosen moniker.
  739.  *
  740.  * @ignore
  741.  */
  742. function _choose_moniker($page, $type, $id, $moniker_src, $no_exists_check_for = null, $scope_context = null)
  743. {
  744.     $moniker = _generate_moniker($moniker_src);
  745.  
  746.     // Check it does not already exist
  747.     $moniker_origin = $moniker;
  748.     $next_num = 1;
  749.     if (is_numeric($moniker)) {
  750.         $moniker .= '-1';
  751.     }
  752.     $test = mixed();
  753.     do {
  754.         if (!is_null($no_exists_check_for)) {
  755.             if ($moniker == preg_replace('#^.*/#', '', $no_exists_check_for)) {
  756.                 return $moniker; // This one is okay, we know it is safe
  757.             }
  758.         }
  759.  
  760.         $dupe_sql = 'SELECT m_resource_id FROM ' . get_table_prefix() . 'url_id_monikers WHERE ';
  761.         $dupe_sql .= db_string_equal_to('m_resource_page', $page);
  762.         if ($type == '') {
  763.             $dupe_sql .= ' AND ' . db_string_equal_to('m_resource_id', $id);
  764.         } else {
  765.             $dupe_sql .= ' AND ' . db_string_equal_to('m_resource_type', $type) . ' AND ' . db_string_not_equal_to('m_resource_id', $id);
  766.         }
  767.         $dupe_sql .= ' AND (';
  768.         if (!is_null($scope_context)) {
  769.             $dupe_sql .= db_string_equal_to('m_moniker', $scope_context . $moniker);
  770.         } else {
  771.             // Use reversing for better indexing performance
  772.             $dupe_sql .= db_string_equal_to('m_moniker_reversed', strrev($moniker));
  773.             $dupe_sql .= ' OR m_moniker_reversed LIKE \'' . db_encode_like(strrev('%/' . $moniker)) . '\'';
  774.         }
  775.         $dupe_sql .= ')';
  776.         $test = $GLOBALS['SITE_DB']->query_value_if_there($dupe_sql, false, true);
  777.         if (!is_null($test)) { // Oh dear, will pass to next iteration, but trying a new moniker
  778.             $next_num++;
  779.             $moniker = $moniker_origin . '-' . strval($next_num);
  780.         }
  781.     } while (!is_null($test));
  782.  
  783.     return $moniker;
  784. }
  785.  
  786. /**
  787.  * Generate a moniker from an arbitrary raw string. Does not perform uniqueness checks.
  788.  *
  789.  * @param  string $moniker_src Raw string.
  790.  * @return ID_TEXT Moniker.
  791.  *
  792.  * @ignore
  793.  */
  794. function _generate_moniker($moniker_src)
  795. {
  796.     $moniker = strip_comcode($moniker_src);
  797.  
  798.     $max_moniker_length = intval(get_option('max_moniker_length'));
  799.  
  800.     // Transliteration first
  801.     if (get_charset() == 'utf-8') {
  802.         if (function_exists('transliterator_transliterate')) {
  803.             $moniker = transliterator_transliterate('Any-Latin; Latin-ASCII; Lower()', $moniker);
  804.         } elseif ((function_exists('iconv')) && (get_value('disable_iconv') !== '1')) {
  805.             $moniker = iconv('utf-8', 'ASCII//TRANSLIT//IGNORE', $moniker);
  806.         } else {
  807.             // German has inbuilt transliteration
  808.             $moniker = str_replace(array('ä', 'ö', 'ü', 'ß'), array('ae', 'oe', 'ue', 'ss'), $moniker);
  809.         }
  810.     }
  811.  
  812.     // Then strip down / substitute to force it to be URL-ready
  813.     $moniker = str_replace("'", '', $moniker);
  814.     $moniker = strtolower(preg_replace('#[^A-Za-z\d\-]#', '-', $moniker));
  815.     if (strlen($moniker) > $max_moniker_length) {
  816.         $pos = strrpos(substr($moniker, 0, $max_moniker_length), '-');
  817.         if (($pos === false) || ($pos < 12)) {
  818.             $pos = $max_moniker_length;
  819.         }
  820.         $moniker = substr($moniker, 0, $pos);
  821.     }
  822.     $moniker = preg_replace('#\-+#', '-', $moniker);
  823.     $moniker = rtrim($moniker, '-');
  824.  
  825.     // A bit lame, but maybe we'll have to
  826.     if ($moniker == '') {
  827.         $moniker = 'untitled';
  828.     }
  829.  
  830.     return $moniker;
  831. }
  832.  
  833. /**
  834.  * Take a moniker and it's page-link details, and make a full path from it.
  835.  *
  836.  * @param  ID_TEXT $page Page name.
  837.  * @param  ID_TEXT $type Screen type code.
  838.  * @param  ID_TEXT $id Resource ID.
  839.  * @param  ID_TEXT $zone The URL zone name (only used for Comcode Page URL monikers).
  840.  * @param  string $main Pathless moniker.
  841.  * @return string The fully qualified moniker.
  842.  *
  843.  * @ignore
  844.  */
  845. function _give_moniker_scope($page, $type, $id, $zone, $main)
  846. {
  847.     // Does this URL arrangement support monikers?
  848.     global $CONTENT_OBS;
  849.     load_moniker_hooks();
  850.     $found = false;
  851.     if ($type == '') {
  852.         $looking_for = '_WILD:_WILD';
  853.     } else {
  854.         $looking_for = '_SEARCH:' . $page . ':' . $type . ':_WILD';
  855.     }
  856.  
  857.     $ob_info = isset($CONTENT_OBS[$looking_for]) ? $CONTENT_OBS[$looking_for] : null;
  858.  
  859.     $moniker = $main;
  860.  
  861.     if (is_null($ob_info)) {
  862.         return $moniker;
  863.     }
  864.  
  865.     if (!is_null($ob_info['parent_category_field'])) {
  866.         if ($ob_info['parent_category_field'] == 'the_zone') {
  867.             $ob_info['parent_category_field'] = 'p_parent_page'; // Special exception for Comcode page monikers
  868.         }
  869.  
  870.         // Lookup DB record so we can discern the category
  871.         $bak = $GLOBALS['NO_DB_SCOPE_CHECK'];
  872.         $GLOBALS['NO_DB_SCOPE_CHECK'] = true;
  873.         require_code('content');
  874.         $select = array();
  875.         append_content_select_for_id($select, $ob_info);
  876.         if (substr($ob_info['title_field'], 0, 5) != 'CALL:') {
  877.             $select[] = $ob_info['title_field'];
  878.         }
  879.         if (!is_null($ob_info['parent_category_field'])) {
  880.             $select[] = $ob_info['parent_category_field'];
  881.         }
  882.         $where = get_content_where_for_str_id(($type == '') ? $page : $id, $ob_info);
  883.         if (isset($where['the_zone'])) {
  884.             $where['the_zone'] = $zone;
  885.         }
  886.         $_moniker_src = $GLOBALS['SITE_DB']->query_select($ob_info['table'], $select, $where);
  887.         $GLOBALS['NO_DB_SCOPE_CHECK'] = $bak;
  888.         if (!array_key_exists(0, $_moniker_src)) {
  889.             return $moniker; // been deleted?
  890.         }
  891.  
  892.         // Discern the path (will effectively recurse, due to find_id_moniker call)
  893.         $parent = $_moniker_src[0][$ob_info['parent_category_field']];
  894.         if (is_integer($parent)) {
  895.             $parent = strval($parent);
  896.         }
  897.         if ((is_null($parent)) || ($parent === 'root') || ($parent === '') || ($parent == strval(db_get_first_id()))) {
  898.             $tree = null;
  899.         } else {
  900.             $view_category_page_link_pattern = explode(':', $ob_info['view_category_page_link_pattern']);
  901.             if ($type == '') {
  902.                 $tree = find_id_moniker(array('page' => $parent), $zone);
  903.             } else {
  904.                 $tree = find_id_moniker(array('page' => $view_category_page_link_pattern[1], 'type' => $view_category_page_link_pattern[2], 'id' => $parent), $zone);
  905.             }
  906.         }
  907.  
  908.         // Okay, so our full tree path is as follows
  909.         if (!is_null($tree)) {
  910.             $moniker = $tree . '/' . $main;
  911.         }
  912.     }
  913.  
  914.     return $moniker;
  915. }
  916.  
  917. /**
  918.  * Take a moniker and it's page-link details, and make a full path from it.
  919.  *
  920.  * @param  ID_TEXT $content_type The content type.
  921.  * @param  SHORT_TEXT $url_moniker The URL moniker.
  922.  * @return ?ID_TEXT The ID (null: not found).
  923.  */
  924. function find_id_via_url_moniker($content_type, $url_moniker)
  925. {
  926.     $path = 'hooks/systems/content_meta_aware/' . filter_naughty($content_type, true);
  927.     if ((!file_exists(get_file_base() . '/sources/' . $path . '.php')) && (!file_exists(get_file_base() . '/sources_custom/' . $path . '.php'))) {
  928.         return null;
  929.     }
  930.  
  931.     require_code($path);
  932.  
  933.     $cma_ob = object_factory('Hook_content_meta_aware_' . $content_type);
  934.     $cma_info = $cma_ob->info();
  935.     if (!$cma_info['support_url_monikers']) {
  936.         return null;
  937.     }
  938.  
  939.     list(, $url_bits) = page_link_decode($cma_info['view_page_link_pattern']);
  940.     $where = array('m_resource_page' => $url_bits['page'], 'm_resource_type' => $url_bits['type'], 'm_moniker' => $url_moniker);
  941.  
  942.     $ret = $cma_info['connection']->query_select_value_if_there('url_id_monikers', 'm_resource_id', $where);
  943.     return $ret;
  944. }
  945.  

Here's the fix to do it:

Code (php)

  1. <?php /*
  2.  
  3.  Composr
  4.  Copyright (c) ocProducts, 2004-2016
  5.  
  6.  See text/EN/licence.txt for full licencing information.
  7.  
  8.  
  9.  NOTE TO PROGRAMMERS:
  10.    Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
  11.    **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****
  12.  
  13. */
  14.  
  15. /**
  16.  * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
  17.  * @copyright  ocProducts Ltd
  18.  * @package    core
  19.  */
  20.  
  21. /**
  22.  * Change whatever global context that is required in order to run from a different context.
  23.  *
  24.  * @sets_input_state
  25.  *
  26.  * @param  array $new_get The URL component map (must contain 'page').
  27.  * @param  ID_TEXT $new_zone The zone.
  28.  * @param  ID_TEXT $new_current_script The running script.
  29.  * @param  boolean $erase_keep_also Whether to get rid of keep_ variables in current URL.
  30.  * @return array A list of parameters that would be required to be passed back to reset the state.
  31.  */
  32. function set_execution_context($new_get, $new_zone = '_SEARCH', $new_current_script = 'index', $erase_keep_also = false)
  33. {
  34.     $old_get = $_GET;
  35.     $old_zone = get_zone_name();
  36.     $old_current_script = current_script();
  37.  
  38.     foreach ($_GET as $key => $val) {
  39.         if (is_integer($key)) {
  40.             $key = strval($key);
  41.         }
  42.  
  43.         if ((substr($key, 0, 5) != 'keep_') || ($erase_keep_also)) {
  44.             unset($_GET[$key]);
  45.         }
  46.     }
  47.  
  48.     foreach ($new_get as $key => $val) {
  49.         $_GET[$key] = is_integer($val) ? strval($val) : $val;
  50.     }
  51.  
  52.     global $RELATIVE_PATH, $ZONE, $SELF_URL_CACHED;
  53.     $RELATIVE_PATH = ($new_zone == '_SEARCH') ? get_page_zone(get_page_name()) : $new_zone;
  54.     if ($new_zone != $old_zone) {
  55.         $ZONE = null; // So zone details will have to reload
  56.     }
  57.     $SELF_URL_CACHED = null;
  58.  
  59.     global $PAGE_NAME_CACHE;
  60.     $PAGE_NAME_CACHE = null;
  61.     global $RUNNING_SCRIPT_CACHE, $WHAT_IS_RUNNING_CACHE;
  62.     $RUNNING_SCRIPT_CACHE = array();
  63.     $WHAT_IS_RUNNING_CACHE = $new_current_script;
  64.  
  65.     return array($old_get, $old_zone, $old_current_script, true);
  66. }
  67.  
  68. /**
  69.  * Map spaces to %20 and put http:// in front of URLs starting www.
  70.  *
  71.  * @param  URLPATH $url The URL to fix
  72.  * @return URLPATH The fixed result
  73.  */
  74. function remove_url_mistakes($url)
  75. {
  76.     if (substr($url, 0, 4) == 'www.') {
  77.         $url = 'http://' . $url;
  78.     }
  79.     $url = @html_entity_decode($url, ENT_NOQUOTES);
  80.     $url = str_replace(' ', '%20', $url);
  81.     $url = preg_replace('#keep_session=\w*#', 'filtered=1', $url);
  82.     return $url;
  83. }
  84.  
  85. /**
  86.  * Get hidden fields for a form representing 'keep_x'. If we are having a GET form instead of a POST form, we need to do this. This function also encodes the page name, as we'll always want that.
  87.  *
  88.  * @param  ID_TEXT $page The page for the form to go to (blank: don't attach)
  89.  * @param  boolean $keep_all Whether to keep all elements of the current URL represented in this form (rather than just the keep_ fields, and page)
  90.  * @param  ?array $exclude A list of parameters to exclude (null: don't exclude any)
  91.  * @return Tempcode The builtup hidden form fields
  92.  *
  93.  * @ignore
  94.  */
  95. function _build_keep_form_fields($page = '', $keep_all = false, $exclude = null)
  96. {
  97.     if (is_null($exclude)) {
  98.         $exclude = array();
  99.     }
  100.  
  101.     if ($page == '_SELF') {
  102.         $page = get_page_name();
  103.     }
  104.     $out = new Tempcode();
  105.  
  106.     if (count($_GET) > 0) {
  107.         foreach ($_GET as $key => $val) {
  108.             $process_for_key = ((substr($key, 0, 5) == 'keep_') || ($keep_all)) && (!in_array($key, $exclude)) && ($key != 'page') && (!skippable_keep($key, $val));
  109.  
  110.             if (is_array($val)) {
  111.                 foreach ($val as $_key => $_val) { // We'll only support one level deep. Also no keep parameter array support.
  112.                     if (get_magic_quotes_gpc()) {
  113.                         $_val = stripslashes($_val);
  114.                     }
  115.  
  116.                     if ($process_for_key) {
  117.                         $out->attach(form_input_hidden($key . '[' . $_key . ']', $_val));
  118.                     }
  119.                 }
  120.             } else {
  121.                 if (!is_string($val)) {
  122.                     continue;
  123.                 }
  124.  
  125.                 if (is_integer($key)) {
  126.                     $key = strval($key);
  127.                 }
  128.  
  129.                 if (get_magic_quotes_gpc()) {
  130.                     $val = stripslashes($val);
  131.                 }
  132.  
  133.                 if ($process_for_key) {
  134.                     $out->attach(form_input_hidden($key, $val));
  135.                 }
  136.             }
  137.         }
  138.     }
  139.     if ($page != '') {
  140.         $out->attach(form_input_hidden('page', $page));
  141.     }
  142.     return $out;
  143. }
  144.  
  145. /**
  146.  * Recurser helper function for _build_keep_post_fields.
  147.  *
  148.  * @param  ID_TEXT $key Key name to put value under
  149.  * @param  mixed $value Value (string or array)
  150.  * @return string The builtup hidden form fields
  151.  *
  152.  * @ignore
  153.  */
  154. function _fixed_post_parser($key, $value)
  155. {
  156.     $out = '';
  157.  
  158.     if (!is_string($key)) {
  159.         $key = strval($key);
  160.     }
  161.  
  162.     if (is_array($value)) {
  163.         foreach ($value as $k => $v) {
  164.             if (is_string($k)) {
  165.                 $out .= _fixed_post_parser($key . '[' . $k . ']', $v);
  166.             } else {
  167.                 $out .= _fixed_post_parser($key . '[' . strval($k) . ']', $v);
  168.             }
  169.         }
  170.     } else {
  171.         if (get_magic_quotes_gpc()) {
  172.             $value = stripslashes($value);
  173.         }
  174.  
  175.         $out .= static_evaluate_tempcode(form_input_hidden($key, is_string($value) ? $value : strval($value)));
  176.     }
  177.  
  178.     return $out;
  179. }
  180.  
  181. /**
  182.  * Relay all POST variables for this URL, to the URL embedded in the form.
  183.  *
  184.  * @param  ?array $exclude A list of parameters to exclude (null: exclude none)
  185.  * @param  boolean $force_everything Force field labels and descriptions to copy through even when there are huge numbers of parameters
  186.  * @return Tempcode The builtup hidden form fields
  187.  *
  188.  * @ignore
  189.  */
  190. function _build_keep_post_fields($exclude = null, $force_everything = false)
  191. {
  192.     $out = '';
  193.     foreach ($_POST as $key => $val) {
  194.         if (is_integer($key)) {
  195.             $key = strval($key);
  196.         }
  197.  
  198.         if (((!is_null($exclude)) && (in_array($key, $exclude))) || ($key == 'session_id'/*for spam blackhole*/)) {
  199.             continue;
  200.         }
  201.  
  202.         if (count($_POST) > 80 && !$force_everything) {
  203.             if (substr($key, 0, 14) == 'tick_on_form__') {
  204.                 continue;
  205.             }
  206.             if (substr($key, 0, 11) == 'label_for__') {
  207.                 continue;
  208.             }
  209.             if (substr($key, 0, 9) == 'require__') {
  210.                 continue;
  211.             }
  212.         }
  213.  
  214.         $out .= _fixed_post_parser($key, $val);
  215.     }
  216.     return make_string_tempcode($out);
  217. }
  218.  
  219. /**
  220.  * Takes a URL, and converts it into a file system storable filename. This is used to cache URL contents to the servers filesystem.
  221.  *
  222.  * @param  URLPATH $url_full The URL to convert to an encoded filename
  223.  * @return string A usable filename based on the URL
  224.  *
  225.  * @ignore
  226.  */
  227. function _url_to_filename($url_full)
  228. {
  229.     $bad_chars = array('!', '/', '\\', '?', '*', '<', '>', '|', '"', ':', '%', ' ');
  230.     $new_name = $url_full;
  231.     foreach ($bad_chars as $bad_char) {
  232.         $good_char = '!' . strval(ord($bad_char));
  233.         if ($bad_char == ':') {
  234.             $good_char = ';'; // So page_links save nice
  235.         }
  236.         $new_name = str_replace($bad_char, $good_char, $new_name);
  237.     }
  238.  
  239.     if (strlen($new_name) <= 200/*technically 256 but something may get put on the start, so be cautious*/) {
  240.         return $new_name;
  241.     }
  242.  
  243.     // Non correspondance, but at least we have something
  244.     if (strpos($new_name, '.') === false) {
  245.         return md5($new_name);
  246.     }
  247.     return md5($new_name) . '.' . get_file_extension($new_name);
  248. }
  249.  
  250. /**
  251.  * Take a URL and base-URL, and fully qualify the URL according to it.
  252.  *
  253.  * @param  URLPATH $url The URL to fully qualified
  254.  * @param  URLPATH $url_base The base-URL
  255.  * @return URLPATH Fully qualified URL
  256.  *
  257.  * @ignore
  258.  */
  259. function _qualify_url($url, $url_base)
  260. {
  261.     require_code('obfuscate');
  262.     $mto = mailto_obfuscated();
  263.     if (($url != '') && ($url[0] != '#') && (substr($url, 0, 5) != 'data:') && (substr($url, 0, 7) != 'mailto:') && (substr($url, 0, strlen($mto)) != $mto)) {
  264.         if (url_is_local($url)) {
  265.             if ($url[0] == '/') {
  266.                 $parsed = @parse_url($url_base);
  267.                 if ($parsed === false) {
  268.                     return '';
  269.                 }
  270.                 if (!array_key_exists('scheme', $parsed)) {
  271.                     $parsed['scheme'] = 'http';
  272.                 }
  273.                 if (!array_key_exists('host', $parsed)) {
  274.                     $parsed['host'] = 'localhost';
  275.                 }
  276.                 if (substr($url, 0, 2) == '//') {
  277.                     $url = $parsed['scheme'] . ':' . $url;
  278.                 } else {
  279.                     $url = $parsed['scheme'] . '://' . $parsed['host'] . (array_key_exists('port', $parsed) ? (':' . $parsed['port']) : '') . $url;
  280.                 }
  281.             } else {
  282.                 $url = $url_base . '/' . $url;
  283.             }
  284.         }
  285.     }
  286.  
  287.     $url = str_replace('/./', '/', $url);
  288.     $pos = strpos($url, '/../');
  289.     while ($pos !== false) {
  290.         $pos_2 = strrpos(substr($url, 0, $pos - 1), '/');
  291.         if ($pos_2 === false) {
  292.             break;
  293.         }
  294.         $url = substr($url, 0, $pos_2) . '/' . substr($url, $pos + 4);
  295.         $pos = strpos($url, '/../');
  296.     }
  297.  
  298.     return $url;
  299. }
  300.  
  301. /**
  302.  * Convert a URL to a local file path.
  303.  *
  304.  * @param  URLPATH $url The value to convert
  305.  * @return ?PATH File path (null: is not local)
  306.  * @ignore
  307.  */
  308. function _convert_url_to_path($url)
  309. {
  310.     if (strpos($url, '?') !== false) {
  311.         return null;
  312.     }
  313.     if ((strpos($url, '://') === false) || (substr($url, 0, strlen(get_base_url()) + 1) == get_base_url() . '/') || (substr($url, 0, strlen(get_custom_base_url()) + 1) == get_custom_base_url() . '/')) {
  314.         if (substr($url, 0, strlen(get_base_url()) + 1) == get_base_url() . '/') {
  315.             $file_path_stub = urldecode(substr($url, strlen(get_base_url()) + 1));
  316.         } elseif (substr($url, 0, strlen(get_custom_base_url()) + 1) == get_custom_base_url() . '/') {
  317.             $file_path_stub = urldecode(substr($url, strlen(get_custom_base_url()) + 1));
  318.         } else {
  319.             $file_path_stub = urldecode($url);
  320.         }
  321.         if (((substr($file_path_stub, 0, 7) == 'themes/') && (substr($file_path_stub, 0, 15) != 'themes/default/')) || (substr($file_path_stub, 0, 8) == 'uploads/') || (strpos($file_path_stub, '_custom/') !== false)) {
  322.             $_file_path_stub = get_custom_file_base() . '/' . $file_path_stub;
  323.             if (!is_file($_file_path_stub)) {
  324.                 $_file_path_stub = get_file_base() . '/' . $file_path_stub;
  325.             }
  326.         } else {
  327.             $_file_path_stub = get_file_base() . '/' . $file_path_stub;
  328.         }
  329.  
  330.         if (!is_file($_file_path_stub)) {
  331.             return null;
  332.         }
  333.  
  334.         return $_file_path_stub;
  335.     }
  336.  
  337.     return null;
  338. }
  339.  
  340. /**
  341.  * Sometimes users don't enter full URLs but do intend for them to be absolute. This code tries to see what relative URLs are actually absolute ones, via an algorithm. It then fixes the URL.
  342.  *
  343.  * @param  URLPATH $in The URL to fix
  344.  * @return URLPATH The fixed URL (or original one if no fix was needed)
  345.  * @ignore
  346.  */
  347. function _fixup_protocolless_urls($in)
  348. {
  349.     if ($in == '') {
  350.         return $in;
  351.     }
  352.  
  353.     $in = remove_url_mistakes($in); // Chain in some other stuff
  354.  
  355.     if (strpos($in, ':') !== false) {
  356.         return $in; // Absolute (e.g. http:// or mailto:)
  357.     }
  358.  
  359.     if (substr($in, 0, 1) == '#') {
  360.         return $in;
  361.     }
  362.     if (substr($in, 0, 1) == '%') {
  363.         return $in;
  364.     }
  365.     if (substr($in, 0, 1) == '{') {
  366.         return $in;
  367.     }
  368.  
  369.     // Rule 1: // If we have a dot somewhere before a slash, then this dot is likely part of a domain name (not a file extension)- thus we have an absolute URL.
  370.     if (preg_match('#\..*/#', $in) != 0) {
  371.         return 'http://' . $in; // Fix it
  372.     }
  373.     // Rule 2: // If we have no slashes and we don't recognise a file type then they've probably just entered a domain name- thus we have an absolute URL.
  374.     if ((preg_match('#^[^/]+$#', $in) != 0) && (preg_match('#\.(php|htm|asp|jsp|swf|gif|png|jpg|jpe|txt|pdf|odt|ods|odp|doc|mdb|xls|ppt|xml|rss|ppt|svg|wrl|vrml|gif|psd|rtf|bmp|avi|mpg|mpe|webm|mp4|mov|wmv|ram|rm|asf|ra|wma|wav|mp3|ogg|torrent|csv|ttf|tar|gz|rar|bz2)#', $in) == 0)) {
  375.         return 'http://' . $in . '/'; // Fix it
  376.     }
  377.  
  378.     return $in; // Relative
  379. }
  380.  
  381. /**
  382.  * Convert a local URL to a page-link.
  383.  *
  384.  * @param  URLPATH $url The URL to convert. Note it may not be a URL Scheme, and it must be based on the local base URL (else failure WILL occur).
  385.  * @param  boolean $abs_only Whether to only convert absolute URLs. Turn this on if you're not sure what you're passing is a URL not and you want to be extra safe.
  386.  * @param  boolean $perfect_only Whether to only allow perfect conversions.
  387.  * @return string The page-link (blank: could not convert).
  388.  *
  389.  * @ignore
  390.  */
  391. function _url_to_page_link($url, $abs_only = false, $perfect_only = true)
  392. {
  393.     if (($abs_only) && (substr($url, 0, 7) != 'http://') && (substr($url, 0, 8) != 'https://')) {
  394.         return '';
  395.     }
  396.  
  397.     // Try and strip any variants of the base URL from our $url variable, to make it relative
  398.     $non_www_base_url = str_replace('https://www.', 'https://', str_replace('http://www.', 'http://', get_base_url()));
  399.     $www_base_url = str_replace('https://', 'https://www.', str_replace('http://', 'http://www.', get_base_url()));
  400.     $url = preg_replace('#^' . preg_quote(get_base_url() . '/', '#') . '#', '', $url);
  401.     $url = preg_replace('#^' . preg_quote($non_www_base_url . '/', '#') . '#', '', $url);
  402.     $url = preg_replace('#^' . preg_quote($www_base_url . '/', '#') . '#', '', $url);
  403.     if (substr($url, 0, 7) == 'http://') {
  404.         return '';
  405.     }
  406.     if (substr($url, 0, 8) == 'https://') {
  407.         return '';
  408.     }
  409.     if (substr($url, 0, 1) != '/') {
  410.         $url = '/' . $url;
  411.     }
  412.  
  413.     // Parse the URL
  414.     $parsed_url = @parse_url($url);
  415.     if ($parsed_url === false) {
  416.         require_code('site');
  417.         attach_message(do_lang_tempcode('HTTP_DOWNLOAD_BAD_URL', escape_html($url)), 'warn');
  418.         return '';
  419.     }
  420.  
  421.     // Work out the zone
  422.     $slash_pos = strpos($parsed_url['path'], '/', 1);
  423.     $zone = ($slash_pos !== false) ? substr($parsed_url['path'], 1, $slash_pos - 1) : '';
  424.     if (!in_array($zone, find_all_zones())) {
  425.         $zone = '';
  426.         $slash_pos = false;
  427.     }
  428.     $parsed_url['path'] = ($slash_pos === false) ? substr($parsed_url['path'], 1) : substr($parsed_url['path'], $slash_pos + 1); // everything AFTER the zone
  429.     $parsed_url['path'] = preg_replace('#/index\.php$#', '', $parsed_url['path']);
  430.     $attributes = array();
  431.     $attributes['page'] = ''; // hopefully will get overwritten with a real one
  432.  
  433.     // Convert URL Scheme path info into extra implied attribute data
  434.     require_code('url_remappings');
  435.     $does_match = false;
  436.     foreach (array('PG', 'HTM', 'SIMPLE', 'RAW') as $url_scheme) {
  437.         $mappings = get_remappings($url_scheme);
  438.         foreach ($mappings as $mapping) { // e.g. array(array('page' => 'wiki', 'id' => null), 'pg/s/ID', true),
  439.             if (is_null($mapping)) {
  440.                 continue;
  441.             }
  442.  
  443.             list($params, $match_string,) = $mapping;
  444.             $match_string_pattern = preg_replace('#[A-Z]+#', '[^\&\?]+', preg_quote($match_string)); // Turn match string into a regexp
  445.  
  446.             $does_match = (preg_match('#^' . $match_string_pattern . '#', $parsed_url['path']) != 0);
  447.             if ($does_match) {
  448.                 $attributes = array_merge($attributes, $params);
  449.  
  450.                 if ($url_scheme == 'HTM') {
  451.                     if (strpos($parsed_url['path'], '.htm') === false) {
  452.                         continue;
  453.                     }
  454.  
  455.                     $_match_string = preg_replace('#\.htm$#', '', $match_string);
  456.                     $_path = preg_replace('#\.htm($|\?)#', '', $parsed_url['path']);
  457.                 } else {
  458.                     if (strpos($parsed_url['path'], '.htm') !== false) {
  459.                         continue;
  460.                     }
  461.  
  462.                     $_match_string = $match_string;
  463.                     $_path = $parsed_url['path'];
  464.                 }
  465.  
  466.                 $bits_pattern = explode('/', $_match_string);
  467.                 $bits_real = explode('/', $_path, count($bits_pattern));
  468.  
  469.                 foreach ($bits_pattern as $i => $bit) {
  470.                     if ((strtoupper($bit) == $bit) && (array_key_exists(strtolower($bit), $params)) && (is_null($params[strtolower($bit)]))) {
  471.                         $attributes[strtolower($bit)] = $bits_real[$i];
  472.                     }
  473.                 }
  474.  
  475.                 foreach ($attributes as &$attribute) {
  476.                     $attribute = cms_url_decode_post_process(urldecode($attribute));
  477.                 }
  478.  
  479.                 break 2;
  480.             }
  481.         }
  482.     }
  483.     if (!$does_match) {
  484.         return ''; // No match was found
  485.     }
  486.  
  487.     // Parse query string component into the waiting (and partly-filled-already) attribute data array
  488.     if (array_key_exists('query', $parsed_url)) {
  489.         $bits = explode('&', $parsed_url['query']);
  490.         foreach ($bits as $bit) {
  491.             $_bit = explode('=', $bit, 2);
  492.  
  493.             if (count($_bit) == 2) {
  494.                 $attributes[$_bit[0]] = cms_url_decode_post_process($_bit[1]);
  495.                 if (strpos($attributes[$_bit[0]], ':') !== false) {
  496.                     if ($perfect_only) {
  497.                         return ''; // Could not convert this URL to a page-link, because it contains a colon
  498.                     }
  499.                     unset($attributes[$_bit[0]]);
  500.                 }
  501.             }
  502.         }
  503.     }
  504.  
  505.     require_code('site');
  506.     if (_request_page($attributes['page'], $zone) === false) {
  507.         return '';
  508.     }
  509.  
  510.     $page = fix_page_name_dashing($zone, $attributes['page']);
  511.  
  512.     // Put it together
  513.     $page_link = $zone . ':' . $page;
  514.     if (array_key_exists('type', $attributes)) {
  515.         $page_link .= ':' . $attributes['type'];
  516.     } elseif (array_key_exists('id', $attributes)) {
  517.         $page_link .= ':';
  518.     }
  519.     if (array_key_exists('id', $attributes)) {
  520.         $page_link .= ':' . $attributes['id'];
  521.     }
  522.     foreach ($attributes as $key => $val) {
  523.         if (!is_string($val)) {
  524.             $val = strval($val);
  525.         }
  526.  
  527.         if (($key != 'page') && ($key != 'type') && ($key != 'id')) {
  528.             $page_link .= ':' . $key . '=' . cms_url_encode($val);
  529.         }
  530.     }
  531.  
  532.     // Hash bit?
  533.     if (array_key_exists('fragment', $parsed_url)) {
  534.         $page_link .= '#' . $parsed_url['fragment'];
  535.     }
  536.  
  537.     return $page_link;
  538. }
  539.  
  540. /**
  541.  * Convert a local page file path to a written page-link.
  542.  *
  543.  * @param  string $page The path.
  544.  * @return string The page-link (blank: could not convert).
  545.  *
  546.  * @ignore
  547.  */
  548. function _page_path_to_page_link($page)
  549. {
  550.     if ((substr($page, 0, 1) == '/') && (substr($page, 0, 6) != '/pages')) {
  551.         $page = substr($page, 1);
  552.     }
  553.     $matches = array();
  554.     if (preg_match('#^([^/]*)/?pages/([^/]+)/(\w\w/)?([^/\.]+)\.(php|txt|htm)$#', $page, $matches) == 1) {
  555.         $page2 = $matches[1] . ':' . $matches[4];
  556.         if (($matches[2] == 'comcode') || ($matches[2] == 'comcode_custom')) {
  557.             if (file_exists(get_custom_file_base() . '/' . $page)) {
  558.                 $file = file_get_contents(get_custom_file_base() . '/' . $page);
  559.                 if (preg_match('#\[title\](.*)\[/title\]#U', $file, $matches) != 0) {
  560.                     $page2 .= ' (' . $matches[1] . ')';
  561.                 } elseif (preg_match('#\[title=[\'"]?1[\'"]?\](.*)\[/title\]#U', $file, $matches) != 0) {
  562.                     $page2 .= ' (' . $matches[1] . ')';
  563.                 }
  564.                 $page2 = preg_replace('#\[[^\[\]]*\]#', '', $page2);
  565.             }
  566.         }
  567.     } else {
  568.         $page2 = '';
  569.     }
  570.  
  571.     return $page2;
  572. }
  573.  
  574. /**
  575.  * Called from 'find_id_moniker'. We tried to lookup a moniker, found a hook, but found no stored moniker. So we'll try and autogenerate one.
  576.  *
  577.  * @param  array $ob_info The hooks info profile.
  578.  * @param  array $url_parts The URL component map (must contain 'page', 'type', and 'id' if this function is to do anything).
  579.  * @param  ID_TEXT $zone The URL zone name (only used for Comcode Page URL monikers).
  580.  * @return ?string The moniker ID (null: error generating it somehow, can not do it)
  581.  */
  582. function autogenerate_new_url_moniker($ob_info, $url_parts, $zone)
  583. {
  584.     $effective_id = ($url_parts['type'] == '') ? $url_parts['page'] : $url_parts['id'];
  585.  
  586.     $bak = $GLOBALS['NO_DB_SCOPE_CHECK'];
  587.     $GLOBALS['NO_DB_SCOPE_CHECK'] = true;
  588.     require_code('content');
  589.     $select = array();
  590.     append_content_select_for_id($select, $ob_info);
  591.     if (substr($ob_info['title_field'], 0, 5) != 'CALL:') {
  592.         $select[] = $ob_info['title_field'];
  593.     }
  594.     if ($ob_info['parent_category_field'] !== null) {
  595.         $select[] = $ob_info['parent_category_field'];
  596.     }
  597.     $db = ((substr($ob_info['table'], 0, 2) != 'f_') || (get_forum_type() == 'none')) ? $GLOBALS['SITE_DB'] : $GLOBALS['FORUM_DB'];
  598.     $where = get_content_where_for_str_id($effective_id, $ob_info);
  599.     if (isset($where['the_zone'])) {
  600.         $where['the_zone'] = $zone;
  601.     }
  602.     $_moniker_src = $db->query_select($ob_info['table'], $select, $where); // NB: For Comcode pages visited, this won't return anything -- it will become more performant when the page actually loads, so the moniker won't need redoing each time
  603.     $GLOBALS['NO_DB_SCOPE_CHECK'] = $bak;
  604.     if (!array_key_exists(0, $_moniker_src)) {
  605.         return null; // been deleted?
  606.     }
  607.  
  608.     if ($ob_info['id_field_numeric']) {
  609.         if (substr($ob_info['title_field'], 0, 5) == 'CALL:') {
  610.             $moniker_src = call_user_func(trim(substr($ob_info['title_field'], 5)), $url_parts);
  611.         } else {
  612.             if ($ob_info['title_field_dereference']) {
  613.                 $moniker_src = get_translated_text($_moniker_src[0][$ob_info['title_field']]);
  614.             } else {
  615.                 $moniker_src = $_moniker_src[0][$ob_info['title_field']];
  616.             }
  617.         }
  618.     } else {
  619.         $moniker_src = $effective_id;
  620.     }
  621.  
  622.     if ($moniker_src == '') {
  623.         $moniker_src = 'untitled';
  624.     }
  625.  
  626.     return suggest_new_idmoniker_for($url_parts['page'], isset($url_parts['type']) ? $url_parts['type'] : '', $url_parts['id'], $zone, $moniker_src, true);
  627. }
  628.  
  629. /**
  630.  * Called when content is added, or edited/moved, based upon a new form field that specifies what moniker to use.
  631.  *
  632.  * @param  ID_TEXT $page Page name.
  633.  * @param  ID_TEXT $type Screen type code.
  634.  * @param  ID_TEXT $id Resource ID.
  635.  * @param  ID_TEXT $zone The URL zone name (only used for Comcode Page URL monikers).
  636.  * @param  string $moniker_src String from which a moniker will be chosen (may not be blank).
  637.  * @param  boolean $is_new Whether we are sure this is a new moniker (makes things more efficient, saves a query).
  638.  * @param  ?string $moniker Actual moniker to use (null: generate from $moniker_src). Usually this is left null.
  639.  * @return string The chosen moniker.
  640.  */
  641. function suggest_new_idmoniker_for($page, $type, $id, $zone, $moniker_src, $is_new = false, $moniker = null)
  642. {
  643.     if (get_option('url_monikers_enabled') == '0') {
  644.         return '';
  645.     }
  646.  
  647.     static $force_called = array();
  648.     $ref = $zone . ':' . $page . ':' . $type . ':' . $id;
  649.     if ($moniker !== null) {
  650.         $force_called[$ref] = $moniker;
  651.     } else {
  652.         if (isset($force_called[$ref])) {
  653.             return $force_called[$ref];
  654.         }
  655.     }
  656.  
  657.     if (!$is_new) {
  658.         $manually_chosen = $GLOBALS['SITE_DB']->query_select_value_if_there('url_id_monikers', 'm_moniker', array('m_manually_chosen' => 1, 'm_resource_page' => $page, 'm_resource_type' => $type, 'm_resource_id' => $id));
  659.         if ($manually_chosen !== null) {
  660.             return $manually_chosen;
  661.         }
  662.  
  663.         // Deprecate old one if already exists
  664.         $old = $GLOBALS['SITE_DB']->query_select_value_if_there('url_id_monikers', 'm_moniker', array('m_resource_page' => $page, 'm_resource_type' => $type, 'm_resource_id' => $id, 'm_deprecated' => 0), 'ORDER BY id DESC');
  665.         if (!is_null($old)) {
  666.             // See if it is same as current
  667.             if ($moniker === null) {
  668.                 $scope = _give_moniker_scope($page, $type, $id, $zone, '');
  669.                 $moniker = $scope . _choose_moniker($page, $type, $id, $moniker_src, $old, $scope);
  670.             }
  671.             if ($moniker == $old) {
  672.                 return $old; // hmm, ok it can stay actually
  673.             }
  674.  
  675.             // It's not. Although, the later call to _choose_moniker will allow us to use the same stem as the current active one, or even re-activate an old deprecated one, so long as it is on this same m_resource_page/m_resource_page/m_resource_id.
  676.  
  677.             // Deprecate
  678.             $GLOBALS['SITE_DB']->query_update('url_id_monikers', array('m_deprecated' => 1), array('m_resource_page' => $page, 'm_resource_type' => $type, 'm_resource_id' => $id, 'm_deprecated' => 0), '', 1); // Deprecate
  679.  
  680.             // Deprecate anything underneath
  681.             global $CONTENT_OBS;
  682.             load_moniker_hooks();
  683.             $looking_for = '_SEARCH:' . $page . ':' . $type . ':_WILD';
  684.             $ob_info = isset($CONTENT_OBS[$looking_for]) ? $CONTENT_OBS[$looking_for] : null;
  685.             if (!is_null($ob_info)) {
  686.                 $parts = explode(':', $ob_info['view_page_link_pattern']);
  687.                 $category_page = $parts[1];
  688.                 $GLOBALS['SITE_DB']->query('UPDATE ' . get_table_prefix() . 'url_id_monikers SET m_deprecated=1 WHERE ' . db_string_equal_to('m_resource_page', $category_page) . ' AND m_moniker LIKE \'' . db_encode_like($old . '/%') . '\''); // Deprecate
  689.             }
  690.         }
  691.     }
  692.  
  693.     if ($moniker === null) {
  694.         if (is_numeric($moniker_src)) {
  695.             $moniker = $id;
  696.         } else {
  697.             $scope = _give_moniker_scope($page, $type, $id, $zone, '');
  698.             $moniker = $scope . _choose_moniker($page, $type, $id, $moniker_src, null, $scope);
  699.  
  700.             if (($page == 'news') && ($type == 'view') && (get_value('google_news_urls') === '1')) {
  701.                 $moniker .= '-' . str_pad($id, 3, '0', STR_PAD_LEFT);
  702.             }
  703.         }
  704.     }
  705.  
  706.     // Insert
  707.     $GLOBALS['SITE_DB']->query_delete('url_id_monikers', array(    // It's possible we're re-activating a deprecated one
  708.                                                                    'm_resource_page' => $page,
  709.                                                                    'm_resource_type' => $type,
  710.                                                                    'm_resource_id' => $id,
  711.                                                                    'm_moniker' => $moniker,
  712.     ), '', 1);
  713.     $GLOBALS['SITE_DB']->query_insert('url_id_monikers', array(
  714.         'm_resource_page' => $page,
  715.         'm_resource_type' => $type,
  716.         'm_resource_id' => $id,
  717.         'm_moniker' => $moniker,
  718.         'm_moniker_reversed' => strrev($moniker),
  719.         'm_deprecated' => 0,
  720.         'm_manually_chosen' => 0,
  721.     ));
  722.  
  723.     global $LOADED_MONIKERS_CACHE;
  724.     $LOADED_MONIKERS_CACHE = array();
  725.  
  726.     return $moniker;
  727. }
  728.  
  729. /**
  730.  * Delete an old moniker, and place a new one.
  731.  *
  732.  * @param  ID_TEXT $page Page name.
  733.  * @param  ID_TEXT $type Screen type code.
  734.  * @param  ID_TEXT $id Resource ID.
  735.  * @param  string $moniker_src String from which a moniker will be chosen (may not be blank).
  736.  * @param  ?string $no_exists_check_for Whether to skip the exists check for a certain moniker (will be used to pass "existing self" for edits) (null: nothing existing to check against).
  737.  * @param  ?string $scope_context Where the moniker will be placed in the moniker URL tree (null: unknown, so make so no duplicates anywhere).
  738.  * @return string Chosen moniker.
  739.  *
  740.  * @ignore
  741.  */
  742. function _choose_moniker($page, $type, $id, $moniker_src, $no_exists_check_for = null, $scope_context = null)
  743. {
  744.     $moniker = _generate_moniker($moniker_src);
  745.  
  746.     // Check it does not already exist
  747.     $moniker_origin = $moniker;
  748.     $next_num = 1;
  749.     if (is_numeric($moniker)) {
  750.         $moniker .= '-1';
  751.     }
  752.     $test = mixed();
  753.     do {
  754.         if (!is_null($no_exists_check_for)) {
  755.             if ($moniker == preg_replace('#^.*/#', '', $no_exists_check_for)) {
  756.                 return $moniker; // This one is okay, we know it is safe
  757.             }
  758.         }
  759.  
  760.         $dupe_sql = 'SELECT m_resource_id FROM ' . get_table_prefix() . 'url_id_monikers WHERE ';
  761.         $dupe_sql .= db_string_equal_to('m_resource_page', $page);
  762.         if ($type == '') {
  763.             $dupe_sql .= ' AND ' . db_string_equal_to('m_resource_id', $id);
  764.         } else {
  765.             $dupe_sql .= ' AND ' . db_string_equal_to('m_resource_type', $type) . ' AND ' . db_string_not_equal_to('m_resource_id', $id);
  766.         }
  767.         $dupe_sql .= ' AND (';
  768.         if (!is_null($scope_context)) {
  769.             $dupe_sql .= db_string_equal_to('m_moniker', $scope_context . $moniker);
  770.         } else {
  771.             // Use reversing for better indexing performance
  772.             $dupe_sql .= db_string_equal_to('m_moniker_reversed', strrev($moniker));
  773.             $dupe_sql .= ' OR m_moniker_reversed LIKE \'' . db_encode_like(strrev('%/' . $moniker)) . '\'';
  774.         }
  775.         $dupe_sql .= ')';
  776.         $test = $GLOBALS['SITE_DB']->query_value_if_there($dupe_sql, false, true);
  777.         if (!is_null($test)) { // Oh dear, will pass to next iteration, but trying a new moniker
  778.             $next_num++;
  779.             $moniker = $moniker_origin . '-' . strval($next_num);
  780.         }
  781.     } while (!is_null($test));
  782.  
  783.     return $moniker;
  784. }
  785.  
  786. /**
  787.  * Generate a moniker from an arbitrary raw string. Does not perform uniqueness checks.
  788.  *
  789.  * @param  string $moniker_src Raw string.
  790.  * @return ID_TEXT Moniker.
  791.  *
  792.  * @ignore
  793.  */
  794. function _generate_moniker($moniker_src)
  795. {
  796.     $moniker = strip_comcode($moniker_src);
  797.  
  798.     $max_moniker_length = intval(get_option('max_moniker_length'));
  799.  
  800.     // Transliteration first
  801.     if (get_charset() == 'utf-8') {
  802.         if (function_exists('transliterator_transliterate')) {
  803.             $moniker = transliterator_transliterate('Any-Latin; Latin-ASCII; Lower()', $moniker);
  804.         } elseif ((function_exists('iconv')) && (get_value('disable_iconv') !== '1')) {
  805.             $moniker = iconv('utf-8', 'ASCII//TRANSLIT//IGNORE', $moniker);
  806.         } else {
  807.             // German has inbuilt transliteration
  808.             $moniker = str_replace(array('ä', 'ö', 'ü', 'ß'), array('ae', 'oe', 'ue', 'ss'), $moniker);
  809.         }
  810.     }
  811.  
  812.     // Then strip down / substitute to force it to be URL-ready
  813.     $moniker = str_replace("'", '', $moniker);
  814.     $moniker = strtolower(preg_replace('#[^A-Za-z\d\-]#', '-', $moniker));
  815.     if (strlen($moniker) > $max_moniker_length) {
  816.         $pos = strrpos(substr($moniker, 0, $max_moniker_length), '-');
  817.         if (($pos === false) || ($pos < 12)) {
  818.             $pos = $max_moniker_length;
  819.         }
  820.         $moniker = substr($moniker, 0, $pos);
  821.     }
  822.     $moniker = preg_replace('#\-+#', '-', $moniker);
  823.     $moniker = rtrim($moniker, '-');
  824.  
  825.     // A bit lame, but maybe we'll have to
  826.     if ($moniker == '') {
  827.         $moniker = 'untitled';
  828.     }
  829.  
  830.     return $moniker;
  831. }
  832.  
  833. /**
  834.  * Take a moniker and it's page-link details, and make a full path from it.
  835.  *
  836.  * @param  ID_TEXT $page Page name.
  837.  * @param  ID_TEXT $type Screen type code.
  838.  * @param  ID_TEXT $id Resource ID.
  839.  * @param  ID_TEXT $zone The URL zone name (only used for Comcode Page URL monikers).
  840.  * @param  string $main Pathless moniker.
  841.  * @return string The fully qualified moniker.
  842.  *
  843.  * @ignore
  844.  */
  845. function _give_moniker_scope($page, $type, $id, $zone, $main)
  846. {
  847.     // Does this URL arrangement support monikers?
  848.     global $CONTENT_OBS;
  849.     load_moniker_hooks();
  850.     $found = false;
  851.     if ($type == '') {
  852.         $looking_for = '_WILD:_WILD';
  853.     } else {
  854.         $looking_for = '_SEARCH:' . $page . ':' . $type . ':_WILD';
  855.     }
  856.  
  857.     $ob_info = isset($CONTENT_OBS[$looking_for]) ? $CONTENT_OBS[$looking_for] : null;
  858.  
  859.     $moniker = $main;
  860.  
  861.     if (is_null($ob_info)) {
  862.         return $moniker;
  863.     }
  864.  
  865.     if (!is_null($ob_info['parent_category_field'])) {
  866.         if ($ob_info['parent_category_field'] == 'the_zone') {
  867.             $ob_info['parent_category_field'] = 'p_parent_page'; // Special exception for Comcode page monikers
  868.         }
  869.  
  870.         // Lookup DB record so we can discern the category
  871.         $bak = $GLOBALS['NO_DB_SCOPE_CHECK'];
  872.         $GLOBALS['NO_DB_SCOPE_CHECK'] = true;
  873.         require_code('content');
  874.         $select = array();
  875.         append_content_select_for_id($select, $ob_info);
  876.         if (substr($ob_info['title_field'], 0, 5) != 'CALL:') {
  877.             $select[] = $ob_info['title_field'];
  878.         }
  879.         if (!is_null($ob_info['parent_category_field'])) {
  880.             $select[] = $ob_info['parent_category_field'];
  881.         }
  882.         $where = get_content_where_for_str_id(($type == '') ? $page : $id, $ob_info);
  883.         if (isset($where['the_zone'])) {
  884.             $where['the_zone'] = $zone;
  885.         }
  886.         $_moniker_src = $GLOBALS['SITE_DB']->query_select($ob_info['table'], $select, $where);
  887.         $GLOBALS['NO_DB_SCOPE_CHECK'] = $bak;
  888.         if (!array_key_exists(0, $_moniker_src)) {
  889.             return $moniker; // been deleted?
  890.         }
  891.  
  892.         // Discern the path (will effectively recurse, due to find_id_moniker call)
  893.         $parent = $_moniker_src[0][$ob_info['parent_category_field']];
  894.         if (is_integer($parent)) {
  895.             $parent = strval($parent);
  896.         }
  897.         if ((is_null($parent)) || ($parent === 'root') || ($parent === '') || ($parent == strval(db_get_first_id()))) {
  898.             $tree = null;
  899.         } else {
  900.             $view_category_page_link_pattern = explode(':', $ob_info['view_category_page_link_pattern']);
  901.             if ($type == '') {
  902.                 $tree = find_id_moniker(array('page' => $parent), $zone);
  903.             } else {
  904.                 $tree = find_id_moniker(array('page' => $view_category_page_link_pattern[1], 'type' => $view_category_page_link_pattern[2], 'id' => $parent), $zone);
  905.             }
  906.         }
  907.  
  908.         // Okay, so our full tree path is as follows
  909.         if (!is_null($tree)) {
  910.             $moniker = $tree . '/' . $main;
  911.         }
  912.     }
  913.  
  914.     return $moniker;
  915. }
  916.  
  917. /**
  918.  * Take a moniker and it's page-link details, and make a full path from it.
  919.  *
  920.  * @param  ID_TEXT $content_type The content type.
  921.  * @param  SHORT_TEXT $url_moniker The URL moniker.
  922.  * @return ?ID_TEXT The ID (null: not found).
  923.  */
  924. function find_id_via_url_moniker($content_type, $url_moniker)
  925. {
  926.     $path = 'hooks/systems/content_meta_aware/' . filter_naughty($content_type, true);
  927.     if ((!file_exists(get_file_base() . '/sources/' . $path . '.php')) && (!file_exists(get_file_base() . '/sources_custom/' . $path . '.php'))) {
  928.         return null;
  929.     }
  930.  
  931.     require_code($path);
  932.  
  933.     $cma_ob = object_factory('Hook_content_meta_aware_' . $content_type);
  934.     $cma_info = $cma_ob->info();
  935.     if (!$cma_info['support_url_monikers']) {
  936.         return null;
  937.     }
  938.  
  939.     list(, $url_bits) = page_link_decode($cma_info['view_page_link_pattern']);
  940.     $where = array('m_resource_page' => $url_bits['page'], 'm_resource_type' => $url_bits['type'], 'm_moniker' => $url_moniker);
  941.  
  942.     $ret = $cma_info['connection']->query_select_value_if_there('url_id_monikers', 'm_resource_id', $where);
  943.     return $ret;
  944. }
  945.  
You need the PHP 'intl' extension installed for it to work, or possibly iconv but I wasn't able to get that transliterating on my Mac so it's a fallback possibility.

Post

Posted
Rating:
#1322
I forgot to mention this applies to new categories. However you can always edit the URL moniker for existing categories to what you want.

two of the three view buttons lead to sub-categories

This is where I'm not following, I don't understand the nature of the structural problem you're having.

Post

Posted
Rating:
#1342
I don't know what is "transliteration" and have not figured out how to install php intl extension on my Godaddy linux hosting using cPanel.

But I find after added the English translation before Chinese characters, the "view" buttons are in order, i.e. jumping to where they should have been.

Thank you Chris for your great and prompt reply.

Post

Posted
Rating:
#1346
Hi,

Transliteration is converting the script. In this case converting Chinese script to English/Latin/Roman script because that's what's used in URLs. See how "你好" automatically became "nin-hao" for the URL?

I'll take a look at GoDaddy's situation.

Post

Posted
Rating:
#1347
I have just made an addon for servers without the PHP intl extension:

Transliteration

This provides transliteration support to those without the PHP intl extension. This is used for URL moniker generation. Licence ------- Artistic License (https://github.com/Behat/Transliterator/blob/master/LICENSE) Additional credits/attributions ------------------------------- Konsta Vesterinen, Jonathan H. Wage, Other unknown developers

View



I think you've given me site access (I haven't gone through my emails fully yet), so if so I'll install it for you.

Post

Posted
Rating:
#1350
Hi,

I deployed the new addon to your site, it's working well.

I did find a problem with the URL monikers, you had monikers in the cms_url_id_monikers table for download categories that didn't exist yet, so when new categories were made it re-used those old monikers. It looks to me like the cms_download_categories AUTO_INCREMENT value got changed somehow, as usually it will never re-use the same ID. I fixed that for you, and then the new monikers were generating nicely with my addon. I think this might have been the problem you were trying to explain that I didn't understand initially.

Post

Posted
Rating:
#1361
Chris, you are so great!

Would it be even better if "/" could be used after transliteration instead of "%2F" in the url, i.e. in "id=tsui-ping-church%2Fjing-bai-bu%2Fjiang-dao-lu-yin"?

Post

Posted
Rating:
#1366
Hi :) ,

Yes, just enable a "URL Scheme" and the ID component will become a URL path component, it'll look much more natural without the % encoding.
4 guests and 0 members have recently viewed this.