View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
4694 | Composr | core_comcode_pages | public | 2021-09-27 03:08 | 2024-01-08 21:26 |
Reporter | Chris Graham | Assigned To | Guest | ||
Priority | normal | Severity | feature | ||
Status | new | Resolution | open | ||
Summary | 4694: Phrase-based translation | ||||
Description | Implement an alternative to the page-by-page translation: allowing pages to be translated by individual phrase. Have a UI for translating the phrases. Phrases would be auto-detected. This has big advantages, mainly that if the original (e.g. English) copy of a page changes, the changes reflect for all languages (as untranslated) rather than everything diverging and needing a lot of diffing by translators to get back in sync. | ||||
Additional Information | I have this implemented for a client, I just need to merge the patch. EDIT: The patch is probably now outdated. | ||||
Tags | Has Patch, Roadmap: Over the horizon, Type: Internationalisation | ||||
Attach Tags | |||||
Attached Files | translation_system.diff (59,679 bytes)
diff --git a/cms/pages/modules/cms_comcode_pages.php b/cms/pages/modules/cms_comcode_pages.php index f2ba8fa9..e6f84fd6 100644 --- a/cms/pages/modules/cms_comcode_pages.php +++ b/cms/pages/modules/cms_comcode_pages.php @@ -141,12 +141,12 @@ class Module_cms_comcode_pages $zone = $page_link_parts[0]; $file = $page_link_parts[1]; + $lang = get_param_string('lang', get_param_string('keep_lang', get_site_default_lang())); + if ($page_link != '') { breadcrumb_set_self(do_lang_tempcode('EDIT')); } - breadcrumb_set_parents(array(array('_SELF:_SELF:browse:lang=' . get_param_string('lang', ''), do_lang_tempcode('COMCODE_PAGES')))); - - $lang = get_param_string('lang', get_site_default_lang()); + breadcrumb_set_parents(array(array('_SELF:_SELF:browse:lang=' . $lang, do_lang_tempcode('COMCODE_PAGES')))); if ($file == '') { $this->title = get_screen_title('COMCODE_PAGE_ADD', true, array(escape_html($zone), escape_html($file))); @@ -237,7 +237,7 @@ class Module_cms_comcode_pages * @param LANGUAGE_NAME $lang The language we are searching for pages of * @param ?array $zone_filter List of zones to limit to (null: none) * @param boolean $check_permissions Whether to check edit permissions - * @return array The map (page link => map [path & row]) + * @return array The map (page link => pair [path & row]) */ public function get_comcode_files_list_disk_search($lang, $zone_filter, $check_permissions = true) { @@ -677,10 +677,22 @@ class Module_cms_comcode_pages foreach (['comcode_custom', 'comcode'] as $page_type) { foreach ((($row['zone'] == '') && (get_option('collapse_user_zones') == '1')) ? ['', 'site'] : [$row['zone']] as $_zone) { foreach (array_unique([get_custom_file_base(), get_file_base()]) as $file_base) { - $test_path = $file_base . (($_zone == '') ? '' : ('/' . $_zone)) . '/pages/' . $page_type . '/' . $_lang . '/' . $row['page'] . '.txt'; - if (is_file($test_path)) { - $translations_found[$_lang] = filemtime($test_path); - continue 4; + if ((get_option('page_translation_method') == 'automatic') || (get_option('page_translation_method') == 'phrases')) { + $test_path = $file_base . (($_zone == '') ? '' : ('/' . $_zone)) . '/pages/' . $page_type . '/' . get_site_default_lang() . '/' . $row['page'] . '.txt.json'; + if (is_file($test_path)) { + $result = json_decode(cms_file_get_contents_safe($test_path), true); + if (isset($result[$_lang])) { + $translations_found[$_lang] = true; + continue 4; + } + } + } + if ((get_option('page_translation_method') == 'automatic') || (get_option('page_translation_method') == 'full_pages')) { + $test_path = $file_base . (($_zone == '') ? '' : ('/' . $_zone)) . '/pages/' . $page_type . '/' . $_lang . '/' . $row['page'] . '.txt'; + if (is_file($test_path)) { + $translations_found[$_lang] = filemtime($test_path); + continue 4; + } } } } @@ -747,7 +759,11 @@ class Module_cms_comcode_pages foreach ($langs as $_lang => $language_full_name) { if (array_key_exists($_lang, $translations_found)) { $translation_time = $translations_found[$_lang]; - $translation_label = do_lang_tempcode('_AGO', escape_html(display_time_period(time() - $translation_time))); + if ($translation_time === true) { + $translation_label = do_lang_tempcode('PHRASES'); + } else { + $translation_label = do_lang_tempcode('_AGO', escape_html(display_time_period(time() - $translation_time))); + } $translation_tooltip = get_timezoned_date($translation_time); } else { $translation_label = do_lang_tempcode('ADD'); @@ -870,11 +886,24 @@ class Module_cms_comcode_pages warn_exit(do_lang_tempcode('BAD_CODENAME')); } - $lang = choose_language(get_screen_title(($file == '') ? 'COMCODE_PAGE_ADD' : 'COMCODE_PAGE_EDIT'), true); + $lang = choose_language($this->title, true); if (is_object($lang)) { return $lang; } + list($file_base, $file_path) = find_comcode_page($lang, $file, $zone); + + $page_translation_method = get_option('page_translation_method'); + if (($lang != get_site_default_lang()) && (($page_translation_method == 'automatic') || ($page_translation_method == 'phrases'))) { + list($file_base_default_lang, $file_path_default_lang) = find_comcode_page(get_site_default_lang(), $file, $zone); + $dynamic_trans_path = $file_base_default_lang . '/' . $file_path_default_lang . '.json'; + if ((is_file($dynamic_trans_path)) || ($page_translation_method == 'phrases') || (get_param_string('page_translation_method', null) === 'phrases')) { + return $this->_edit_dynamic_trans(); + } elseif ((strpos($file_path, '/' . $lang . '/') === false) && ($page_translation_method == 'automatic') && (get_param_string('page_translation_method', null) !== 'full_pages')) { + return $this->_choose_page_translation_method(); + } + } + if ((get_param_string('page_template', null) === null) && (get_param_integer('may_choose_template', 0) == 1) && (get_value('page_template_always_ask', '0', true) == '1')) { $template_list = create_selection_list_page_templates(); @@ -908,8 +937,6 @@ class Module_cms_comcode_pages } } - list($file_base, $file_path) = find_comcode_page($lang, $file, $zone); - // Check no redirects in our way if (addon_installed('redirects_editor')) { $test = $GLOBALS['SITE_DB']->query_select_value_if_there('redirects', 'r_to_zone', array('r_from_page' => $file, 'r_from_zone' => $zone)); @@ -1148,6 +1175,248 @@ class Module_cms_comcode_pages )); } + protected function _choose_page_translation_method() + { + require_code('form_templates'); + + require_lang('config'); + + $text = do_lang_tempcode('CONFIG_OPTION_page_translation_method'); + + $fields = new Tempcode(); + + $radios = new Tempcode(); + $radios->attach(form_input_radio_entry('page_translation_method', 'full_pages', false, do_lang_tempcode('CONFIG_OPTION_page_translation_method_VALUE_full_pages'))); + $radios->attach(form_input_radio_entry('page_translation_method', 'phrases', false, do_lang_tempcode('CONFIG_OPTION_page_translation_method_VALUE_phrases'))); + $fields->attach(form_input_radio(do_lang_tempcode('PAGE_TRANSLATION_METHOD'), '', 'page_translation_method', $radios, true)); + + $post_url = get_self_url(false, false, null, false, true); + + return do_template('FORM_SCREEN', array( + 'GET' => true, + 'SKIP_WEBSTANDARDS' => true, + 'HIDDEN' => '', + 'SUBMIT_ICON' => 'buttons__proceed', + 'SUBMIT_NAME' => do_lang_tempcode('PROCEED'), + 'TITLE' => $this->title, + 'FIELDS' => $fields, + 'URL' => $post_url, + 'TEXT' => $text, + 'JAVASCRIPT' => '', + )); + } + + /** + * UI/actualiser for doing dynamic translation for a Comcode page. + * + * @return Tempcode The UI + */ + protected function _edit_dynamic_trans() + { + require_code('lang_dynamic_trans'); + require_code('site2'); + require_code('users_active_actions'); + + $zone = $this->zone; + $codename = $this->file; + + $lang = choose_language($this->title, true); + + $javascript = ''; + + $text = do_lang_tempcode( + 'TRANSLATE_PAGE_PHRASES', + escape_html(lookup_language_full_name(get_site_default_lang())), + escape_html(lookup_language_full_name($lang)), + array( + escape_html($lang), + escape_html($zone), + escape_html($codename), + build_url(array('page' => $codename, 'keep_lang' => get_site_default_lang()), $zone), + build_url(array('page' => $codename, 'keep_lang' => $lang), $zone), + ) + ); + + // Load + list($file_base_default_lang, $string_default_lang) = find_comcode_page(get_site_default_lang(), $codename, $zone); + $dynamic_trans_path = $file_base_default_lang . '/' . $string_default_lang . '.json'; + $dynamic_trans = is_file($dynamic_trans_path) ? json_decode(cms_file_get_contents_safe($dynamic_trans_path), true) : array(); + + // Load from other pages too + $dynamic_trans_other = array(); + $pages_on_disk = $this->get_comcode_files_list_disk_search(get_site_default_lang(), null, false); + foreach ($pages_on_disk as $page_on_disk) { + $test_path = get_custom_file_base() . '/' . $page_on_disk[0] . '.json'; + if (!is_file($test_path)) { + $test_path = get_file_base() . '/' . $page_on_disk[0] . '.json'; + } + if (is_file($test_path)) { + $_result = json_decode(cms_file_get_contents_safe($test_path), true); + if (isset($_result[$lang])) { + $dynamic_trans_other += $_result[$lang]; + } + } + } + + // Get full context of original language... + + $_comcode_page_row = $GLOBALS['SITE_DB']->query_select('comcode_pages', array('*'), array('the_zone' => $zone, 'the_page' => $codename), '', 1); + $comcode_page_row = array_key_exists(0, $_comcode_page_row) ? $_comcode_page_row[0] : null; + + if ($comcode_page_row === null) { + $page_submitter = get_first_admin_user(); + } else { + $page_submitter = $comcode_page_row['p_submitter']; + } + + list($file_base, $file_path) = find_comcode_page($lang, $codename, $zone); + list($html, $comcode) = __load_comcode_page_from_disk($file_path, $zone, $codename, $file_base, $page_submitter, array($this, '_strip_non_translatable')); + + $translator = new HTMLDynamicTranslator(); + $result = $translator->extract_html_text_phrases($html->evaluate(), true); + + // Save + if (post_param_integer('saving_dynamic_trans', 0) == 1) { + if (post_param_integer('delete_unused', 0) == 1) { + unset($dynamic_trans[$lang]); + } + + foreach ($result['phrases'] as $string_key => $phrase) { + $string = post_param_string('phrase_' . $string_key, null); + + if ($string !== null) { + $string = trim($string); + + if ($string != '') { + if (!$phrase['string_is_html']) { + $string = escape_html($string); + } + + $changed = ($phrase['string'] != $string); + if ($changed) { + $dynamic_trans[$lang][$phrase['string']] = $string; + } else { + unset($dynamic_trans[$lang][$phrase['string']]); + } + } + } + } + + if (empty($dynamic_trans)) { + @unlink($dynamic_trans_path) or intelligent_write_error($dynamic_trans_path); + } else { + cms_file_put_contents_safe($dynamic_trans_path, json_encode($dynamic_trans, JSON_PRETTY_PRINT), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE); + } + + require_code('zones3'); + empty_cache_for_comcode_page($zone, $codename); + + attach_message(do_lang_tempcode('SUCCESS_SAVE'), 'inform'); + } + + // UI... + + $fields = new Tempcode(); + + $i = 0; + $has_auto_copy = false; + foreach ($result['phrases'] as $string_key => $phrase) { + $name = 'phrase_' . $string_key; + $pretty_name = do_lang_tempcode($phrase['string_is_html'] ? 'PHRASE_X_HTML' : 'PHRASE_X_PLAIN', escape_html(integer_format($i + 1))); + $description = protect_from_escaping('“' . ($phrase['string_is_html'] ? $phrase['string'] : escape_html($phrase['string'])) . '”.'); + if (isset($dynamic_trans[$lang][$phrase['string']])) { + $default = $dynamic_trans[$lang][$phrase['string']]; + if (!$phrase['string_is_html']) { + $default = html_entity_decode($default, ENT_QUOTES, get_charset()); + } + } elseif (isset($dynamic_trans_other[$phrase['string']])) { + $default = $dynamic_trans_other[$phrase['string']]; + if (!$phrase['string_is_html']) { + $default = html_entity_decode($default, ENT_QUOTES, get_charset()); + } + + $pretty_name = do_lang_tempcode('TRANSLATION_COPIED_FROM', $pretty_name); + + $has_auto_copy = true; + } else { + $default = $phrase['string']; + } + + $field = form_input_text($pretty_name, $description, $name, $default, false, null, true, null, substr_count($phrase['string'], "\n") + 1); + + $fields->attach($field); + + $javascript .= ' + document.getElementById(\'' . $name . '\').onmousedown=function() { this.just_clicked=true; }; + document.getElementById(\'' . $name . '\').onfocus=function() { if (typeof this.just_clicked==\'undefined\') this.select(); delete this.just_clicked; }; + document.getElementById(\'' . $name . '\').onkeyup=function(event) { if (event.keyCode==45) { this.value=this.value.substring(0,this.selectionStart)+\'<a href="{1}">\'+this.value.substring(this.selectionStart,this.selectionEnd)+\'</a>\'+this.value.substring(this.selectionEnd,this.value.length); } }; + '; + + $i++; + } + + $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('TITLE' => do_lang_tempcode('ACTIONS')))); + $fields->attach(form_input_tick(do_lang_tempcode('DELETE_UNUSED_PHRASES'), do_lang_tempcode('DESCRIPTION_DELETE_UNUSED_PHRASES'), 'delete_unused', false)); + + $post_url = get_self_url(false, false, array('page_translation_method' => 'phrases', 'lang' => $lang)); + + $hidden = new Tempcode(); + $hidden->attach(form_input_hidden('saving_dynamic_trans', '1')); + + if ($has_auto_copy) { + $text->attach(paragraph(do_lang_tempcode('TRANSLATIONS_COPIED_FROM'))); + } + + // Indicators for preview (makes it simpler) + $hidden->attach(form_input_hidden('zone', $zone)); + $hidden->attach(form_input_hidden('file', $codename)); + $hidden->attach(form_input_hidden('lang', $lang)); + + return do_template('FORM_SCREEN', array( + 'SKIP_WEBSTANDARDS' => true, + 'HIDDEN' => $hidden, + 'SUBMIT_ICON' => 'buttons__save', + 'SUBMIT_NAME' => do_lang_tempcode('SAVE'), + 'TITLE' => $this->title, + 'FIELDS' => $fields, + 'URL' => $post_url, + 'TEXT' => $text, + 'JAVASCRIPT' => $javascript, + 'PREVIEW' => true, + )); + } + + /** + * Strip Comcode that should not be evaluated and put through the translation system. + * + * @param string $comcode In + * @return string Out + */ + public function _strip_non_translatable($comcode) + { + $comcode = preg_replace('#\[block([\s=].*)?\]\w+\[/block\]#Ui', '', $comcode); + $comcode = preg_replace('#{\$BLOCK,[^{}*]}#Ui', '', $comcode); + + do { + $block_tempcode_pos = strpos($comcode, '{$BLOCK'); + if ($block_tempcode_pos !== false) { + $len = strlen($comcode); + $balance = 1; + for ($i = $block_tempcode_pos + 1; ($i < $len) && ($balance != 0); $i++) { + $c = $comcode[$i]; + if ($c == '{') { + $balance++; + } elseif ($c == '}') { + $balance--; + } + } + $comcode = substr($comcode, 0, $block_tempcode_pos) . substr($comcode, $i); + } + } while ($block_tempcode_pos !== false); + + return $comcode; + } + /** * Whether the Comcode page editor has integrated menu editing. * diff --git a/lang/EN/config.ini b/lang/EN/config.ini index 68aa8068..0c7e8597 100644 --- a/lang/EN/config.ini +++ b/lang/EN/config.ini @@ -533,3 +533,9 @@ CONFIG_OPTION_wysiwyg_font_units_VALUE_em=Element-relative, <kbd>em</kbd> (dynam IPSTACK_API_KEY=ipstack API key CONFIG_OPTION_ipstack_api_key=ipstack is used for gathering advanced IP address information to automatically attach to contact forms. A <a href="https://ipstack.com/product" target="_blank" title="Register for ipstack (this link will open in a new window)">free key</a> is available. + +PAGE_TRANSLATION_METHOD=Page translation method +CONFIG_OPTION_page_translation_method=Select which page translation method to use. +CONFIG_OPTION_page_translation_method_VALUE_automatic=(Selected on a page-by-page basis) +CONFIG_OPTION_page_translation_method_VALUE_full_pages=Translate whole pages (monolithic) +CONFIG_OPTION_page_translation_method_VALUE_phrases=Translate individual phrases (granular) diff --git a/lang/EN/zones.ini b/lang/EN/zones.ini index f5d87510..712d0a10 100644 --- a/lang/EN/zones.ini +++ b/lang/EN/zones.ini @@ -111,3 +111,12 @@ DESCRIPTION_INCLUDE_ON_SITEMAP=Whether this page will be shown on the sitemap, s SYNC_REVISIONS_WITH_GIT=Sync revisions with Git SYNC_REVISIONS_WITH_GIT_CONFIRM=When manually editing page files and using a revision control system, the revision information within the software will be incomplete. This tool will go over the Git revision history to create page revisions for any commits within Git to your pages, as well as bump forward edit dates in the database as required. For this tool to work the <kbd>git</kbd> executable must be in the path and callable from PHP. SYNC_REVISIONS_WITH_GIT_MESSAGE=Synched {1} {1|revision|revisions} from Git. + +TRANSLATE_PAGE_PHRASES=This screen lets you translate all the phrases in the default <a href="{6}" target="_blank" title="{1} version (this link will open in a new window)">{1} version</a> of <kbd>{4}:{5}</kbd> to the <a href="{7}" target="_blank" title="{2} version (this link will open in a new window)" />{2} version</a>.<!--<br />You can insert a parameterised hyperlink around a text selection by pressing the insert key, which will work so long as the original text also has such a link.--><br />If you need to reset any phrase back to {1}, you can just leave it blank.<br />Note that any referenced media files in the File/Media library can be automatically substituted via a file naming convention; e.g. if you have <kbd>example.jpg</kbd> then <kbd>example_{3}.jpg</kbd> will be used if it exists. +PHRASE_X_HTML=Phrase {1} (HTML) +PHRASE_X_PLAIN=Phrase {1} (Plain) +PHRASES=Phrases +DELETE_UNUSED_PHRASES=Delete unused phrases +DESCRIPTION_DELETE_UNUSED_PHRASES=Delete phrases that can not be seen in a current scan of the page. These may be old translated phrases no longer present in the page, or phrases that that only come up in certain conditions. Use this option with a lot of care. +TRANSLATION_COPIED_FROM={1} <span class="red_alert">†</span> +TRANSLATIONS_COPIED_FROM=<span class="red_alert">†</span> Translations were copied from other pages automatically. diff --git a/sources/hooks/systems/config/page_translation_method.php b/sources/hooks/systems/config/page_translation_method.php new file mode 100644 index 00000000..d0f351aa --- /dev/null +++ b/sources/hooks/systems/config/page_translation_method.php @@ -0,0 +1,55 @@ +<?php /* + + Composr + Copyright (c) ocProducts, 2004-2016 + + See text/EN/licence.txt for full licencing information. + + + NOTE TO PROGRAMMERS: + Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder + **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes **** + +*/ + +/** + * @license http://opensource.org/licenses/cpal_1.0 Common Public Attribution License + * @copyright ocProducts Ltd + * @package core_language_editing + */ + +/** + * Hook class. + */ +class Hook_config_page_translation_method +{ + /** + * Gets the details relating to the config option. + * + * @return ?array The details (null: disabled) + */ + public function get_details() + { + return array( + 'human_name' => 'PAGE_TRANSLATION_METHOD', + 'type' => 'list', + 'category' => 'SITE', + 'group' => 'INTERNATIONALISATION', + 'explanation' => 'CONFIG_OPTION_page_translation_method', + 'shared_hosting_restricted' => '0', + 'list_options' => 'automatic|full_pages|phrases', + + 'addon' => 'core_language_editing', + ); + } + + /** + * Gets the default value for the config option. + * + * @return ?string The default value (null: option is disabled) + */ + public function get_default() + { + return 'automatic'; + } +} diff --git a/sources/hooks/systems/preview/comcode_page.php b/sources/hooks/systems/preview/comcode_page.php index f7222da0..e6ebe7fc 100644 --- a/sources/hooks/systems/preview/comcode_page.php +++ b/sources/hooks/systems/preview/comcode_page.php @@ -31,6 +31,11 @@ class Hook_preview_comcode_page public function applies() { $applies = (get_page_name() == 'cms_comcode_pages'); + + if (post_param_integer('saving_dynamic_trans', 0) == 1) { + return array($applies, null, false); + } + return array($applies, 'comcode_page', false, array('post')); } @@ -44,6 +49,47 @@ class Hook_preview_comcode_page $codename = post_param_string('file'); $zone = post_param_string('zone'); + if (post_param_integer('saving_dynamic_trans', 0) == 1) { + require_code('site2'); + require_code('lang_dynamic_trans'); + + $lang = post_param_string('lang'); + + $_comcode_page_row = $GLOBALS['SITE_DB']->query_select('comcode_pages', array('*'), array('the_zone' => $zone, 'the_page' => $codename), '', 1); + $comcode_page_row = array_key_exists(0, $_comcode_page_row) ? $_comcode_page_row[0] : null; + + if ($comcode_page_row === null) { + $page_submitter = get_first_admin_user(); + } else { + $page_submitter = $comcode_page_row['p_submitter']; + } + + list($file_base, $file_path) = find_comcode_page($lang, $codename, $zone); + list($html) = __load_comcode_page_from_disk($file_path, $zone, $codename, $file_base, $page_submitter); + + $dynamic_trans = array(); + + $translator = new HTMLDynamicTranslator(); + $result = $translator->extract_html_text_phrases($html->evaluate(), true); + foreach ($result['phrases'] as $string_key => $phrase) { + $string = post_param_string('phrase_' . $string_key, null); + + if ($string !== null) { + $string = trim($string); + + if ($string != '') { + if (!$phrase['string_is_html']) { + $string = escape_html($string); + } + + $dynamic_trans[$lang][$phrase['string']] = $string; + } + } + } + + return array(lang_dynamic_trans($dynamic_trans, $html, $lang), null); + } + $original_comcode = post_param_string('post'); $posting_ref_id = post_param_integer('posting_ref_id', mt_rand(0, mt_getrandmax())); diff --git a/sources/lang3.php b/sources/lang3.php index 14453af8..ed8eb775 100644 --- a/sources/lang3.php +++ b/sources/lang3.php @@ -33,8 +33,7 @@ function _choose_language($title, $tip = false, $allow_all_selection = false) return user_lang(); } - $lang = get_param_string('lang', /*get_param_string('keep_lang', null)*/ - null); + $lang = get_param_string('lang', /*get_param_string('keep_lang', null)*/get_param_string('keep_lang', null)); if ($lang !== null) { return filter_naughty($lang); } diff --git a/sources/lang_dynamic_trans.php b/sources/lang_dynamic_trans.php new file mode 100644 index 00000000..9ccff9a1 --- /dev/null +++ b/sources/lang_dynamic_trans.php @@ -0,0 +1,555 @@ +<?php + +/** + * Dynamically translate a Comcode page based on existing phrase translations. + * + * @param array $dynamic_trans Details of what translation to do + * @param Tempcode $html Untranslated version of the page + * @param LANGUAGE_NAME $lang Language to translate into + * @return Tempcode Translated version of the page + */ +function lang_dynamic_trans($dynamic_trans, $html, $lang) +{ + $html_evaluated = $html->evaluate($lang); + + $translator = new HTMLDynamicTranslator(); + $result = $translator->extract_html_text_phrases($html->evaluate()); + + if ((isset($dynamic_trans[$lang])) && (!empty($result['phrases']))) { + $_html_evaluated = ''; + + $last_end = 0; + + foreach ($result['phrases'] as $phrase_details) { + $_html_evaluated .= substr($html_evaluated, $last_end, $phrase_details['start'] - $last_end); + + $portion = substr($html_evaluated, $phrase_details['start'], $phrase_details['end'] - $phrase_details['start']); + + $string = $phrase_details['string']; + + $string_html = $phrase_details['string_html']; + if (isset($dynamic_trans[$lang][$string])) { + $string_html_regexp = '#^' . preg_quote($string_html, '#') . '$#'; + + $translated = $dynamic_trans[$lang][$string]; + foreach ($phrase_details['params'] as $i => $param) { + $translated = str_replace('{' . strval($i + 1) . '}', $param, $translated); + $string_html_regexp = str_replace('\{' . strval($i + 1) . '\}', '.*', $string_html_regexp); + } + + $_html_evaluated .= preg_replace($string_html_regexp, $translated, $portion); + } else { + $_html_evaluated .= $portion; + } + + $last_end = $phrase_details['end']; + } + + $_html_evaluated .= substr($html_evaluated, $last_end); + + $html_evaluated = $_html_evaluated; + } + + $stub = get_custom_file_base() . '/uploads/filedump/'; + foreach ($result['urls_to_substitute'] as $url) { + $path = convert_url_to_path($url); + if (($path !== null) && (substr($path, 0, strlen($stub)) == $stub)) { + $ext = get_file_extension($path); + $path = substr($path, 0, strlen($path) - 1 - strlen($ext)) . '_' . $lang . '.' . $ext; + if (is_file($path)) { + $html_evaluated = str_replace($url, substr($url, 0, strlen($url) - 1 - strlen($ext)) . '_' . $lang . '.' . $ext, $html_evaluated); + } + } + } + + $matches = array(); + if (preg_match('#<h1[^<>]*>(.*)</h1>#Ui', $html_evaluated, $matches) != 0) { + $title_to_use = protect_from_escaping($matches[1]); + get_screen_title($title_to_use, false); // Little hack - this will force DISPLAYED_TITLE to get set. + } + + return make_string_tempcode($html_evaluated); +} + +/** + * Class to parse some HTML for finding translatable phrases. + */ +class HTMLDynamicTranslator +{ + const PARSE_NEUTRAL = 1; + const PARSE_IN_INLINE_TAG = 2; + const PARSE_IN_INLINE_ANCHOR_TAG = 3; + const PARSE_IN_MEDIA_TAG = 4; + const PARSE_IN_INPUT_BUTTON_TAG = 5; + const PARSE_IN_SCRIPT_TAG = 6; + const PARSE_IN_TAG = 7; + const PARSE_IN_ATTRIBUTE_DOUBLE = 8; + const PARSE_IN_ATTRIBUTE_SINGLE = 9; + const PARSE_IN_ATTRIBUTE_DOUBLE_SKIP = 10; + const PARSE_IN_ATTRIBUTE_SINGLE_SKIP = 11; + const PARSE_IN_COMMENT = 12; + + protected $phrases = array(); + protected $urls_to_substitute = array(); + + // Working + protected $current_phrase = null; + protected $current_phrase_start = null; + protected $current_phrase_params = array(); + + /** + * Parse some HTML. + * + * @param string $html HTML to parse + * @param boolean $deduplicate Whether to deduplicate the strings + * @return array A structure of what was parsed + */ + public function extract_html_text_phrases($html, $deduplicate = false) + { + $inline_tags = array_flip(array( + 'em', + 'i', + 'strong', + 'b', + 'u', + 'span', + 'abbr', + 'acronym', + 'kbd', + 'samp', + 'tt', + 'q', + 'var', + 'small', + 'big', + 'sub', + 'sup', + 'time', + //'a', Parsed separately + )); + + $media_tags = array_flip(array( + 'img', + 'embed', + 'input', + 'video', + 'source', + 'audio', + 'track', + )); + + $i = 0; + $mode = self::PARSE_NEUTRAL; + $previous_mode_stack = array(); + $len = strlen($html); + $coming_tag = ''; + for ($i = 0; $i < $len; $i++) { + $c = $html[$i]; + + switch ($mode) { + case self::PARSE_NEUTRAL: + switch ($c) { + case '<': + if (substr($html, $i, 4) == '<!--') { + $this->conclude_phrase($html, $i); + array_push($previous_mode_stack, $mode); + $mode = self::PARSE_IN_COMMENT; + break; + } + + $matches = array(); + preg_match('#\G/?(\w*)#', $html, $matches, 0, $i + 1); + $coming_tag = strtolower($matches[1]); + if (($coming_tag == 'input') && (preg_match('#\G<input[^<>]*type="(submit|button)"[^<>]*/>#Uis', $html, $matches, 0, $i) != 0)) { + $this->conclude_phrase($html, $i); + array_push($previous_mode_stack, $mode); + $mode = self::PARSE_IN_INPUT_BUTTON_TAG; + break; + } elseif (isset($media_tags[$coming_tag])) { + $this->conclude_phrase($html, $i); + array_push($previous_mode_stack, $mode); + $mode = self::PARSE_IN_MEDIA_TAG; + break; + } elseif ($coming_tag == 'script') { + $this->conclude_phrase($html, $i); + array_push($previous_mode_stack, $mode); + $mode = self::PARSE_IN_SCRIPT_TAG; + } elseif ($coming_tag == 'a') { + if ((trim($this->current_phrase) == '') && (preg_match('#\G<a[^<>]*></a>#Uis', $html, $matches, 0, $i) != 0)) { + // Empty link... + $i += strlen($matches[0]) - 1; + break; + } elseif ((trim($this->current_phrase) == '') && (preg_match('#\G(<a[^<>]*>)([^<>]*)</a>[^<]*\w#Uis', $html, $matches, 0, $i) == 0) && (preg_match('#\G(<a[^<>]*>)(.*)</a>#Uis', $html, $matches, 0, $i) != 0)) { + // Wraps some text that is not followed by other text... + + $translator = new HTMLDynamicTranslator(); + $result = $translator->extract_html_text_phrases($matches[2]); + foreach ($result['phrases'] as $phrase) { + $phrase['start'] += $i + strlen($matches[1]); + $phrase['end'] += $i + strlen($matches[1]); + + $this->phrases[] = $phrase; + } + $this->urls_to_substitute = array_merge($this->urls_to_substitute, $result['urls_to_substitute']); + + $i += strlen($matches[0]) - 1; + break; + } else { + array_push($previous_mode_stack, $mode); + $mode = self::PARSE_IN_INLINE_ANCHOR_TAG; + } + } elseif (!isset($inline_tags[$coming_tag])) { + $this->conclude_phrase($html, $i); + array_push($previous_mode_stack, $mode); + $mode = self::PARSE_IN_TAG; + break; + } else { + array_push($previous_mode_stack, $mode); + $mode = self::PARSE_IN_INLINE_TAG; + } + // no break + + default: + $this->init_or_extend_phrase($i, $c); + break; + } + break; + + case self::PARSE_IN_INLINE_TAG: + switch ($c) { + case '>': + $mode = array_pop($previous_mode_stack); + // no break + + default: + $this->init_or_extend_phrase($i, $c); + break; + } + break; + + case self::PARSE_IN_SCRIPT_TAG: + switch ($c) { + case '<': + if (substr($html, $i, 9) == '</script>') { + $mode = array_pop($previous_mode_stack); + $i += 8; + } + break; + } + break; + + case self::PARSE_IN_INLINE_ANCHOR_TAG: + switch ($c) { + case 'h': + $matches = array(); + if (preg_match('#\Ghref="([^"]*)"#', $html, $matches, 0, $i) != 0) { + $this->current_phrase_params[] = $matches[1]; + $parameterised_href = 'href="{' . strval(count($this->current_phrase_params)) . '}"'; + $this->init_or_extend_phrase($i, $parameterised_href); + $i += strlen($matches[0]) - 1; + break; + } + + $this->init_or_extend_phrase($i, $c); + break; + + case '>': + $mode = array_pop($previous_mode_stack); + // no break + + default: + $this->init_or_extend_phrase($i, $c); + break; + } + break; + + case self::PARSE_IN_INPUT_BUTTON_TAG: + switch ($c) { + case '>': + $mode = array_pop($previous_mode_stack); + break; + + case '"': + array_push($previous_mode_stack, $mode); + $this->conclude_phrase($html, $i); + if ($this->just_passed_attribute($html, $i, 'value')) { + $mode = self::PARSE_IN_ATTRIBUTE_DOUBLE; + } else { + $mode = self::PARSE_IN_ATTRIBUTE_DOUBLE_SKIP; + } + break; + + case "'": + array_push($previous_mode_stack, $mode); + $this->conclude_phrase($html, $i); + if ($this->just_passed_attribute($html, $i, 'value')) { + $mode = self::PARSE_IN_ATTRIBUTE_SINGLE; + } else { + $mode = self::PARSE_IN_ATTRIBUTE_SINGLE_SKIP; + } + break; + } + break; + + case self::PARSE_IN_MEDIA_TAG: + switch ($c) { + case 's': + $matches = array(); + if (preg_match('#\Gsrc="([^"]*)"#', $html, $matches, 0, $i) != 0) { + $this->urls_to_substitute[] = $matches[1]; + $i += strlen($matches[0]) - 1; + break; + } elseif (preg_match('#\Gsrcset="([^"]*)"#', $html, $matches, 0, $i) != 0) { + $parts = explode(',', $matches[1]); + foreach ($parts as $part) { + list($part_first) = explode(' ', trim($part)); + $this->urls_to_substitute[] = $part_first; + } + $i += strlen($matches[0]) - 1; + break; + } + + break; + + case '>': + $mode = array_pop($previous_mode_stack); + break; + + case '"': + array_push($previous_mode_stack, $mode); + $this->conclude_phrase($html, $i); + if ($this->just_passed_text_attribute($html, $i)) { + $mode = self::PARSE_IN_ATTRIBUTE_DOUBLE; + } else { + $mode = self::PARSE_IN_ATTRIBUTE_DOUBLE_SKIP; + } + break; + + case "'": + array_push($previous_mode_stack, $mode); + $this->conclude_phrase($html, $i); + if ($this->just_passed_text_attribute($html, $i)) { + $mode = self::PARSE_IN_ATTRIBUTE_SINGLE; + } else { + $mode = self::PARSE_IN_ATTRIBUTE_SINGLE_SKIP; + } + break; + } + break; + + case self::PARSE_IN_TAG: + switch ($c) { + case '>': + $mode = array_pop($previous_mode_stack); + break; + + case '"': + array_push($previous_mode_stack, $mode); + $this->conclude_phrase($html, $i); + if ($this->just_passed_text_attribute($html, $i)) { + $mode = self::PARSE_IN_ATTRIBUTE_DOUBLE; + } else { + $mode = self::PARSE_IN_ATTRIBUTE_DOUBLE_SKIP; + } + break; + + case "'": + array_push($previous_mode_stack, $mode); + $this->conclude_phrase($html, $i); + if ($this->just_passed_text_attribute($html, $i)) { + $mode = self::PARSE_IN_ATTRIBUTE_SINGLE; + } else { + $mode = self::PARSE_IN_ATTRIBUTE_SINGLE_SKIP; + } + break; + } + break; + + case self::PARSE_IN_ATTRIBUTE_DOUBLE: + switch ($c) { + case '"': + $this->conclude_phrase($html, $i); + $mode = array_pop($previous_mode_stack); + break; + + default: + $this->init_or_extend_phrase($i, $c); + break; + } + break; + + case self::PARSE_IN_ATTRIBUTE_SINGLE: + switch ($c) { + case "'": + $this->conclude_phrase($html, $i); + $mode = array_pop($previous_mode_stack); + break; + + default: + $this->init_or_extend_phrase($i, $c); + break; + } + break; + + case self::PARSE_IN_ATTRIBUTE_DOUBLE_SKIP: + switch ($c) { + case '"': + $mode = array_pop($previous_mode_stack); + break; + } + break; + + case self::PARSE_IN_ATTRIBUTE_SINGLE_SKIP: + switch ($c) { + case "'": + $mode = array_pop($previous_mode_stack); + break; + } + break; + + case self::PARSE_IN_COMMENT: + switch ($c) { + case '-': + if (substr($html, $i, 3) == '-->') { + $mode = array_pop($previous_mode_stack); + $i += 2; + break; + } + } + break; + } + } + + $this->conclude_phrase($html, $i); + + if ($deduplicate) { + $phrases = array(); + foreach ($this->phrases as $phrase) { + $phrases[md5($phrase['string'])] = $phrase; + } + } else { + $phrases = $this->phrases; + } + + return array( + 'phrases' => $phrases, + 'urls_to_substitute' => $this->urls_to_substitute, + ); + } + + /** + * See if the parser has got to the point where a text attribute of an HTML tag is about to be given its value. + * + * @param string $html HTML + * @param integer $i Parser position in HTML + * @return boolean Whether it has + */ + protected function just_passed_text_attribute($html, $i) + { + foreach (array('alt', 'title', 'summary', 'placeholder') as $attribute) { + if ($this->just_passed_attribute($html, $i, $attribute)) { + return true; + } + } + return false; + } + + /** + * See if the parser has got to the point where a particular attribute of an HTML tag is about to be given its value. + * + * @param string $html HTML + * @param integer $i Parser position in HTML + * @param string $attribute Attribute name + * @return boolean Whether it has + */ + protected function just_passed_attribute($html, $i, $attribute) + { + return (strtolower(substr($html, $i - strlen($attribute) - 1, strlen($attribute))) == $attribute); + } + + /** + * Initialise or extend a phrase being parsed. + * + * @param integer $i Parser position + * @param string $c Character(s) + */ + protected function init_or_extend_phrase($i, $c) + { + if ($this->current_phrase === null) { + if (trim($c) == '') { + return; + } + + $this->current_phrase = ''; + $this->current_phrase_start = $i; + } + + $this->current_phrase .= $c; + } + + /** + * Conclude a phrase just parsed into our data structure (used internally be the parser). + * + * @param string $html HTML being parsed + * @param integer $i Parser position + */ + protected function conclude_phrase($html, $i) + { + $phrase = $this->current_phrase; + + $start = $this->current_phrase_start; + $end = $i; + + // Strip leading closing tags + $len1 = strlen($phrase); + $phrase = preg_replace('#^(\s*</\w+>)+#', '', $phrase); + $len2 = strlen($phrase); + $start += $len1 - $len2; + + // Strip trailing opening tags + $len1 = strlen($phrase); + $phrase = preg_replace('#(<\w+[^<>]*>\s*)+$#', '', $phrase); + $len2 = strlen($phrase); + $end -= $len1 - $len2; + + // Trim from front + $len1 = strlen($phrase); + $phrase = ltrim($phrase); + $len2 = strlen($phrase); + $start += $len1 - $len2; + + // Trim from end + $len1 = strlen($phrase); + $phrase = rtrim($phrase); + $len2 = strlen($phrase); + $end -= $len1 - $len2; + + if (strip_tags($phrase) != '') { + $charset = function_exists('get_charset') ? get_charset() : 'utf-8'; + $decoded = html_entity_decode($phrase, ENT_QUOTES, $charset); + if (htmlentities($decoded, ENT_QUOTES, $charset) == $phrase) { + $string_is_html = false; + $string = $decoded; + } else { + $string_is_html = true; + $string = $phrase; + } + + $phrase_details = array( + 'string' => $string, + 'string_is_html' => $string_is_html, + 'string_html' => $phrase, + 'params' => $this->current_phrase_params, + + 'start' => $start, + 'end' => $end, + ); + $this->phrases[] = $phrase_details; + + //$check = substr($html, $start, $end - $start);@var_dump($check);@var_dump($this->phrases[md5($phrase)]); For debugging + } + $this->current_phrase = null; + $this->current_phrase_start = null; + $this->current_phrase_params = array(); + } +} diff --git a/sources/site.php b/sources/site.php index 756a3939..35ac2f48 100644 --- a/sources/site.php +++ b/sources/site.php @@ -1872,6 +1872,31 @@ function load_comcode_page($string, $zone, $codename, $file_base = null, $being_ list($html, $comcode_page_row, $title_to_use, $raw_comcode) = _load_comcode_page_cache_off($string, $zone, $codename, $file_base, $new_comcode_page_row, $being_included); } + // Dynamic translation mode (works via string substitutions) + // Has to happen after Comcode page caching, as otherwise we would flatten blocks and other Tempcode + if (get_option('page_translation_method') == 'phrases') { + require_code('lang_dynamic_trans'); + list($file_base_default_lang, $string_default_lang) = find_comcode_page(get_site_default_lang(), $codename, $zone); + $dynamic_trans_path = $file_base_default_lang . '/' . $string_default_lang . '.json'; + } elseif (get_option('page_translation_method') == 'automatic') { + list($file_base_default_lang, $string_default_lang) = find_comcode_page(get_site_default_lang(), $codename, $zone); + $_dynamic_trans_path = $file_base_default_lang . '/' . $string_default_lang . '.json'; + if (is_file($_dynamic_trans_path)) { + $dynamic_trans_path = $_dynamic_trans_path; + } + } + if ($dynamic_trans_path !== null) { + if (file_exists($dynamic_trans_path)) { + $dynamic_trans = json_decode(cms_file_get_contents_safe($dynamic_trans_path), true); + } else { + $dynamic_trans = array(); + } + + require_code('lang_dynamic_trans'); + $html->handle_symbol_preprocessing(); + $html = lang_dynamic_trans($dynamic_trans, $html, user_lang()); + } + require_code('global4'); if ( (!comcode_page_include_on_sitemap($zone, $codename, $comcode_page_row)) && diff --git a/sources/site2.php b/sources/site2.php index 07672dbe..7143d562 100644 --- a/sources/site2.php +++ b/sources/site2.php @@ -321,6 +321,45 @@ function page_not_found($codename, $zone) return do_template('MISSING_SCREEN', array('_GUID' => '22f371577cd2ba437e7b0cb241931575', 'TITLE' => $title, 'DID_MEAN' => $_did_mean, 'ADD_URL' => $add_url, 'ADD_REDIRECT_URL' => $add_redirect_url, 'PAGE' => $codename)); } +/** + * Load Comcode page from disk. + * + * @param PATH $string The relative (to Composr's base directory) path to the page (e.g. pages/comcode/EN/start.txt) + * @param ID_TEXT $zone The zone the page is being loaded from + * @param ID_TEXT $codename The codename of the page + * @param PATH $file_base The file base to load from + * @param MEMBER $page_submitter Page submitter + * @param ?mixed $filter Filter to alter Comcode (null: no filter) + * @return array A pair: The page HTML, The page Comcode + * + * @ignore + */ +function __load_comcode_page_from_disk($string, $zone, $codename, $file_base, $page_submitter, $filter = null) +{ + $comcode = cms_file_get_contents_safe($file_base . '/' . $string); + if (strpos($string, '_custom/') === false) { + global $LANG_FILTER_OB; + $comcode = $LANG_FILTER_OB->compile_time(null, $comcode); + } + apply_comcode_page_substitutions($comcode); + $comcode = fix_bad_unicode($comcode); + + if ($filter !== null) { + $comcode = call_user_func($filter, $comcode); + } + + // Parse and work out how to add + global $LAX_COMCODE; + $temp = $LAX_COMCODE; + $LAX_COMCODE = true; + require_code('attachments2'); + $_new = do_comcode_attachments($comcode, 'comcode_page', $zone . ':' . $codename, false, null, false, $page_submitter); + $html = $_new['tempcode']; + $LAX_COMCODE = $temp; + + return array($html, $comcode); +} + /** * Load Comcode page from disk, then cache it. * @@ -342,47 +381,27 @@ function _load_comcode_page_not_cached($string, $zone, $codename, $file_base, $c $nql_backup = $GLOBALS['NO_QUERY_LIMIT']; $GLOBALS['NO_QUERY_LIMIT'] = true; - // Not cached :( - $comcode = cms_file_get_contents_safe($file_base . '/' . $string); - if (strpos($string, '_custom/') === false) { - global $LANG_FILTER_OB; - $comcode = $LANG_FILTER_OB->compile_time(null, $comcode); - } - apply_comcode_page_substitutions($comcode); - $comcode = fix_bad_unicode($comcode); - - if (is_null($new_comcode_page_row['p_submitter'])) { - $as_admin = true; + if ($new_comcode_page_row['p_submitter'] === null) { require_code('users_active_actions'); $new_comcode_page_row['p_submitter'] = get_first_admin_user(); } - if (is_null($comcode_page_row)) { // Default page. We need to find an admin to assign it to. + if ($comcode_page_row === null) { // Default/manually-constructed page $page_submitter = $new_comcode_page_row['p_submitter']; } else { - $as_admin = false; // Will only have admin privileges if $page_submitter has them $page_submitter = $comcode_page_row['p_submitter']; } - if (is_null($page_submitter)) { - $page_submitter = get_member(); - } - // Parse and work out how to add - $lang = user_lang(); - global $LAX_COMCODE; - $temp = $LAX_COMCODE; - $LAX_COMCODE = true; - require_code('attachments2'); - $_new = do_comcode_attachments($comcode, 'comcode_page', $zone . ':' . $codename, false, null, $as_admin/*Ideally we assign $page_submitter based on this as well so it is safe if the Comcode cache is emptied*/, $page_submitter); - $_text_parsed = $_new['tempcode']; - $LAX_COMCODE = $temp; + list($html, $comcode) = __load_comcode_page_from_disk($string, $zone, $codename, $file_base, $page_submitter); // Flatten for performance reasons? if (strpos($comcode, '{$,Quick Cache}') !== false) { - $_text_parsed = apply_quick_caching($_text_parsed); + $html = apply_quick_caching($html); } - $text_parsed = $_text_parsed->to_assembly(); + $text_parsed = $html->to_assembly(); + + $lang = user_lang(); // Check it still needs inserting (it might actually be there, but not translated) $trans_key = $GLOBALS['SITE_DB']->query_select_value_if_there('cached_comcode_pages', 'string_index', array('the_page' => $codename, 'the_zone' => $zone, 'the_theme' => $GLOBALS['FORUM_DRIVER']->get_theme())); @@ -473,7 +492,7 @@ function _load_comcode_page_not_cached($string, $zone, $codename, $file_base, $c $GLOBALS['NO_QUERY_LIMIT'] = $nql_backup; - return array($_text_parsed, $title_to_use, $comcode_page_row, $comcode); + return array($html, $title_to_use, $comcode_page_row, $comcode); } /** @@ -506,40 +525,27 @@ function apply_comcode_page_substitutions(&$comcode) */ function _load_comcode_page_cache_off($string, $zone, $codename, $file_base, $new_comcode_page_row, $being_included = false) { - global $COMCODE_PARSE_TITLE; - - if (is_null($new_comcode_page_row['p_submitter'])) { - $as_admin = true; - $members = $GLOBALS['FORUM_DRIVER']->member_group_query($GLOBALS['FORUM_DRIVER']->get_super_admin_groups(), 1); - if (count($members) != 0) { - $new_comcode_page_row['p_submitter'] = $GLOBALS['FORUM_DRIVER']->mrow_id($members[key($members)]); - } else { - $new_comcode_page_row['p_submitter'] = db_get_first_id() + 1; // On Conversr and most forums, this is the first admin member - } + if ($new_comcode_page_row['p_submitter'] === null) { + require_code('users_active_actions'); + $new_comcode_page_row['p_submitter'] = get_first_admin_user(); } $_comcode_page_row = $GLOBALS['SITE_DB']->query_select('comcode_pages', array('*'), array('the_zone' => $zone, 'the_page' => $codename), '', 1); + $comcode_page_row = array_key_exists(0, $_comcode_page_row) ? $_comcode_page_row[0] : null; - $comcode = cms_file_get_contents_safe($file_base . '/' . $string); - if (strpos($string, '_custom/') === false) { - global $LANG_FILTER_OB; - $comcode = $LANG_FILTER_OB->compile_time(null, $comcode); + if ($comcode_page_row === null) { // Default/manually-constructed page + $page_submitter = $new_comcode_page_row['p_submitter']; + } else { + $page_submitter = $comcode_page_row['p_submitter']; } - apply_comcode_page_substitutions($comcode); - global $LAX_COMCODE; - $temp = $LAX_COMCODE; - $LAX_COMCODE = true; - require_code('attachments2'); - $_new = do_comcode_attachments($comcode, 'comcode_page', $zone . ':' . $codename, false, null, (!array_key_exists(0, $_comcode_page_row)) || (is_guest($_comcode_page_row[0]['p_submitter'])), array_key_exists(0, $_comcode_page_row) ? $_comcode_page_row[0]['p_submitter'] : get_member()); - $html = $_new['tempcode']; - $LAX_COMCODE = $temp; + list($html, $comcode) = __load_comcode_page_from_disk($string, $zone, $codename, $file_base, $page_submitter); + + global $COMCODE_PARSE_TITLE; $title_to_use = is_null($COMCODE_PARSE_TITLE) ? null : clean_html_title($COMCODE_PARSE_TITLE); // Try and insert corresponding page; will silently fail if already exists. This is only going to add a row for a page that was not created in-system - if (array_key_exists(0, $_comcode_page_row)) { - $comcode_page_row = $_comcode_page_row[0]; - } else { + if ($comcode_page_row === null) { $comcode_page_row = $new_comcode_page_row; $GLOBALS['SITE_DB']->query_insert('comcode_pages', $comcode_page_row, false, true); diff --git a/sources/zones3.php b/sources/zones3.php index 6d5f1c12..c06a2f63 100644 --- a/sources/zones3.php +++ b/sources/zones3.php @@ -651,16 +651,7 @@ function save_comcode_page($zone, $new_file, $lang, $text, $validated = 1, $incl } // Empty caching - erase_persistent_cache(); - //persistent_cache_delete(array('PAGE_INFO')); Already erases above - decache('main_comcode_page_children'); - decache('menu'); - $caches = $GLOBALS['SITE_DB']->query_select('cached_comcode_pages', array('string_index'), array('the_zone' => $zone, 'the_page' => $file)); - $GLOBALS['SITE_DB']->query_delete('cached_comcode_pages', array('the_zone' => $zone, 'the_page' => $file)); - foreach ($caches as $cache) { - delete_lang($cache['string_index']); - } - $GLOBALS['COMCODE_PAGE_RUNTIME_CACHE'] = array(); + empty_cache_for_comcode_page($zone, $file); // Log log_it('COMCODE_PAGE_EDIT', $new_file, $zone); @@ -689,6 +680,26 @@ function save_comcode_page($zone, $new_file, $lang, $text, $validated = 1, $incl return $full_path; } +/** + * Empty caching related to a particular Comcode page. + * + * @param ID_TEXT $zone The old zone + * @param ID_TEXT $file The old page + */ +function empty_cache_for_comcode_page($zone, $file) +{ + erase_persistent_cache(); + //persistent_cache_delete(array('PAGE_INFO')); Already erases above + decache('main_comcode_page_children'); + decache('menu'); + $caches = $GLOBALS['SITE_DB']->query_select('cached_comcode_pages', array('string_index'), array('the_zone' => $zone, 'the_page' => $file)); + $GLOBALS['SITE_DB']->query_delete('cached_comcode_pages', array('the_zone' => $zone, 'the_page' => $file)); + foreach ($caches as $cache) { + delete_lang($cache['string_index']); + } + $GLOBALS['COMCODE_PAGE_RUNTIME_CACHE'] = array(); +} + /** * Rename/move a Comcode page. * Does create a redirect, if requested. | ||||
Time estimation (hours) | 2 | ||||
Sponsorship open | |||||
Date Modified | Username | Field | Change |
---|---|---|---|
2021-09-27 03:08 | Chris Graham | New Issue | |
2021-09-27 03:08 | Chris Graham | Tag Attached: Roadmap: v12 | |
2021-09-27 03:08 | Chris Graham | Tag Attached: Type: Internationalisation | |
2021-11-01 20:12 | Chris Graham | Tag Attached: Has Patch | |
2021-11-01 22:22 | Chris Graham | File Added: translation_system.diff | |
2022-08-15 16:58 | Chris Graham | Tag Detached: Roadmap: v12 | |
2022-08-15 16:58 | Chris Graham | Tag Attached: Roadmap: v11 | |
2022-08-15 20:43 | Chris Graham | Assigned To | => Chris Graham |
2022-08-15 20:43 | Chris Graham | Status | Not Assigned => Assigned |
2022-11-20 03:02 | Chris Graham | Tag Detached: Roadmap: v11 | |
2022-11-20 03:03 | Chris Graham | Tag Attached: Roadmap: v12 | |
2022-11-20 03:03 | Chris Graham | Assigned To | Chris Graham => |
2022-11-20 03:03 | Chris Graham | Status | Assigned => Not Assigned |
2023-02-21 02:04 | Chris Graham | Additional Information Updated | |
2024-03-26 00:58 | PDStig | Tag Renamed | Roadmap: v12 => Roadmap: Over the horizon |